C++链表
2016-09-07 12:23
429 查看
链表操作总结:
链表是一种重要的数据结构。它是动态分配存储的一种结构。它可以根据需要开辟存储单元。
链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素,链表中的每一个元素
称为“结点”,每个结点包含两部分:1.实际数据data 2.下一个结点的地址next。表尾的地址部分存放“NULL”.
表示链表到此结束。
链表的各类操作包括:学习单向链表的创建、删除、 插入(无序、有序)、输出、 排序(选择、插入、冒泡)、反序等等。
那么链表该如何遍历呢,遍历链表需要从头到尾,访问每一个元素,直到链表尾。也就是说不断地访问当前节点的next,直到NULL。下面是链表的遍历输出:
链表相对于数组有个非常明显的优点就是能以时间复杂度o(1)完成一个节点的插入或者删除操作。
1.递归实现单链表逆置
tips:
(1)逆置不是指将原有链表逆序打印,逆置破坏了原有链表的结构;
(2)既然需要用递归实现,那首先要明白递归的本质。
我在接触递归时,常常纠结与递归的实现过程,根本没有理解递归的本质,我现在觉得,递归只需要关注以下几点:
算法是否可以用递归实现。感觉递归类似于循环,条件允许范围内一直做某事;
递归时,函数做了哪些操作
递归的终止条件
2.判断两个链表是否相交
思路:如果两个链表相交了,那么交点肯定在最后一个结点,因此问题转化为求链表最后一个结点。
各自求显得麻烦,可以先判断链表的长度,len1 和 len2,长的链表先走 | len1 - len2 |步,然后开始同时走。
3.判断链表是否有环
(1)最简单的一种情况就是链表最后一个结点指向了head,如果最后一个结点指向了null,则没有环路,否则会一直绕圈圈。
(2)链表并非从一开始就进入环路,而是从中间某一结点开始
想法1:最笨的方法就是将current_node与之前的结点比较,如果发现曾经出现过,那么就存在环路。但这种方法需要将之前遍历过的结点全部保存下来,然后依次与当前结点进行比较,很麻烦。
想法2:p1->next , p2->next->next
假设链表有n个结点,未进入环路的结点有k个,则存在于环路中的结点为(n-k)个
现假设p1 p2都已经进入了环路,那么在t时刻,
p1->(v*t+1)%(n-k)
p2->(2*v*t+1)%(n-k)
如果存在环路,则 (v*t+1)%(n-k) = (2*v*t+1)%(n-k) 在t 时刻,p1和p2相遇
4.求链表的倒数第k个结点
这道题目可以转化为求正数第(n-k)个结点
5.单链表排序
如果直接对链表的元素进行排序,那就需要交换指针,这样会很麻烦,其实是我不擅于操作指针罢了,那么对于一个菜鸟来说,最方便的就是将链表中的元素放到数组中,然后再对数组中元素进行快排。但这样需要另外开辟空间。
首先附上快排的代码:
我们仅需要做的就是将链表中元素放到数组中,然后数组再形成新的有序链表。
6.合并两个有序链表
这道题目就相当于把一个结点插入有序链表,时间复杂度为O(length1)+O(length2)
把一个结点插入有序链表的代码如下:
有了上述代码,合并两个有序链表就变得非常简单了。
6.删除当前current_node
与正常删除链表结点不同,这道题未提供头指针,所以比较有意思。
由于只知道current_node,它之前的结点无法得知,所以无法用pre_node->next = current_node->next;
但是可以用current_node->next = current_node->next->next; 即delete current_node->next
链表是一种重要的数据结构。它是动态分配存储的一种结构。它可以根据需要开辟存储单元。
链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素,链表中的每一个元素
称为“结点”,每个结点包含两部分:1.实际数据data 2.下一个结点的地址next。表尾的地址部分存放“NULL”.
表示链表到此结束。
链表的各类操作包括:学习单向链表的创建、删除、 插入(无序、有序)、输出、 排序(选择、插入、冒泡)、反序等等。
那么链表该如何遍历呢,遍历链表需要从头到尾,访问每一个元素,直到链表尾。也就是说不断地访问当前节点的next,直到NULL。下面是链表的遍历输出:
#include <iostream> using namespace std; class node { public: int value; node *next; node() { value = 0; next = NULL; } }; int main() { node *head,*curr; head = new node(); head->next = NULL; head->value = 15; for (size_t i = 0; i < 10; i++) { curr = new node(); curr->value = i; curr->next = head; head = curr; } while (head!=NULL) { cout << head->value << endl; head = head->next; } }
链表相对于数组有个非常明显的优点就是能以时间复杂度o(1)完成一个节点的插入或者删除操作。
1.递归实现单链表逆置
tips:
(1)逆置不是指将原有链表逆序打印,逆置破坏了原有链表的结构;
(2)既然需要用递归实现,那首先要明白递归的本质。
我在接触递归时,常常纠结与递归的实现过程,根本没有理解递归的本质,我现在觉得,递归只需要关注以下几点:
算法是否可以用递归实现。感觉递归类似于循环,条件允许范围内一直做某事;
递归时,函数做了哪些操作
递归的终止条件
node* list_reverse(node* head) { if(head==NULL || head->next==NULL) return head; //每次函数执行时带来的next_node都依次入栈 //最后栈顶保存的是原链表的最后一个结点 node* next_node = list_reverse(head->next); head->next->next = head; head->next = NULL; return next_node; }
2.判断两个链表是否相交
思路:如果两个链表相交了,那么交点肯定在最后一个结点,因此问题转化为求链表最后一个结点。
各自求显得麻烦,可以先判断链表的长度,len1 和 len2,长的链表先走 | len1 - len2 |步,然后开始同时走。
int length_list(node* head) { int count=0; node* current_n 4000 ode = head; if(current_node==NULL) return 0; else if(current_node->next==NULL) return 1; else{ while(current_node->next!=NULL){ count++; current_node = current_node->next; } } return count; } void intersection_twoLists(node* head1,node* head2) { if(head1==NULL || head2==NULL) return; else{ int length1 = length_list(head1); int length2 = length_list(head2); int step; if(length1 >= length2){ step = length1 - length2; for(int i=1;i<=step;i++){ head1 = head1->next; } }else{ step = length2 - length1; for(int i=1;i<=step;i++){ head2 = head2->next; } } while(head1->next!=NULL && head2->next!=NULL){ head1 = head1->next; head2 = head2->next; } if(head1->data == head2->data){ cout<<"intersection"; }else{ cout<<"not intersection"; } } }
3.判断链表是否有环
(1)最简单的一种情况就是链表最后一个结点指向了head,如果最后一个结点指向了null,则没有环路,否则会一直绕圈圈。
void loop_list_VersionFirst(node* head) { node* current_node = head; while(current_node->next!=NULL){ current_node = current_node->next; } if(current_node->next==head){ cout<<"has loop"<<endl; }else{ cout<<"no loop"<<endl; } }
(2)链表并非从一开始就进入环路,而是从中间某一结点开始
想法1:最笨的方法就是将current_node与之前的结点比较,如果发现曾经出现过,那么就存在环路。但这种方法需要将之前遍历过的结点全部保存下来,然后依次与当前结点进行比较,很麻烦。
想法2:p1->next , p2->next->next
假设链表有n个结点,未进入环路的结点有k个,则存在于环路中的结点为(n-k)个
现假设p1 p2都已经进入了环路,那么在t时刻,
p1->(v*t+1)%(n-k)
p2->(2*v*t+1)%(n-k)
如果存在环路,则 (v*t+1)%(n-k) = (2*v*t+1)%(n-k) 在t 时刻,p1和p2相遇
bool hasLoop_list(node* head) { bool loop = false; node* p1 = head; node* p2 = head; if(p1==NULL || p1->next==NULL || p1->next->next==NULL) return false; else{ while(p2->next!=NULL){ p1 = p1->next; p2 = p2->next->next; } if(p1 == p2) loop = true; } return loop; }
4.求链表的倒数第k个结点
这道题目可以转化为求正数第(n-k)个结点
int node_countFromEnd(node* head,int k) { int step = length_list(head)-k; //cout<<length_list(head); //cout<<"step is "<<step; node* current_node_k = head; for(int i=0;i<step;i++){ current_node_k = current_node_k->next; } return current_node_k->data; }
5.单链表排序
如果直接对链表的元素进行排序,那就需要交换指针,这样会很麻烦,其实是我不擅于操作指针罢了,那么对于一个菜鸟来说,最方便的就是将链表中的元素放到数组中,然后再对数组中元素进行快排。但这样需要另外开辟空间。
首先附上快排的代码:
int middle_PathQucik(int* arr,int start_index,int end_index) { int i = start_index-1; int flag = arr[end_index]; for(int j=start_index;j<end_index;j++){ if(arr[j]<flag){ i+=1; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i+1]; arr[i+1] = arr[end_index]; arr[end_index] = temp; return i+1; } void quick_sort(int* arr,int start_index,int end_index) { if(start_index>=end_index) return; int middle = middle_PathQucik(arr,start_index,end_index); quick_sort(arr,start_index,middle-1); quick_sort(arr,middle+1,end_index); }
我们仅需要做的就是将链表中元素放到数组中,然后数组再形成新的有序链表。
void list_sort(node* head) { node* current_node = head; int length_arr = length_list(head); int arr[length_arr]; for(int i=0;i<length_arr;i++){ current_node = current_node->next; arr[i] = current_node->data; } quick_sort(arr,0,length_arr-1); node* new_head = init_list(arr,length_arr); list_traversal(new_head); }
6.合并两个有序链表
这道题目就相当于把一个结点插入有序链表,时间复杂度为O(length1)+O(length2)
把一个结点插入有序链表的代码如下:
void elementInsertOrderList(node* head,int value) { node* current_node = head; node* pre_node; node* insert_node = init_node(value); while(current_node->next!=NULL){ pre_node = current_node; current_node = current_node->next; } if(current_node->data>=value){ pre_node->next = insert_node; insert_node->next = current_node; }else{ current_node->next=insert_node; } list_traversal(head); cout<<endl; }
有了上述代码,合并两个有序链表就变得非常简单了。
void mergeTwoOrderLists(node* head1,node* head2) { node* current_node = head2; int length2 = length_list(head2); for(int i=0;i<length2;i++){ current_node = current_node->next; elementInsertOrderList(head1,current_node->data); } }
6.删除当前current_node
与正常删除链表结点不同,这道题未提供头指针,所以比较有意思。
void delete_currentnode(node* current_node){ node* next_node = current_node->next; current_node->data = next_node->data; current_node->next = next_node->next; delete(next_node); }
由于只知道current_node,它之前的结点无法得知,所以无法用pre_node->next = current_node->next;
但是可以用current_node->next = current_node->next->next; 即delete current_node->next
相关文章推荐
- mac 查看C++及各种环境的命令
- 标准C++_01_编程基础
- 基础备忘:类的成员初始化表与构造函数内赋值操作
- C++ 函数模板(十四)--template 泛型函数模板、通用函数、重载模板
- ubuntu下使用visual studio code来编译和调试C++
- PTA 4-11 求自定类型元素序列的中位数 (25分)
- 项目分析
- 删除数组中重复元素--哈希表方法和set方法
- C++:多态公有继承
- C++如何实现程序到托盘
- VC++ 获取操作系统的版本GetVersionEx函数
- C++学习笔记(五)opencv在win下的使用 —参考浅墨opencv3编程入门
- error C2065: 'IDD_MONDATAREPLYDLG' : undeclared identifier解决方法
- RabbitMQ-c学习和开发client经验分享
- 【《C++ Primer Plus》读书笔记】异常
- C++ 最简真分数(九度OJ 1465)
- Violate的应用和作用
- 我的黑科技,让C++可以访问private成员
- C语言基础学习基本数据类型-字符专属的输入输出函数
- C++ 知识点(五):高级编程:文件和流,异常处理,动态内存,命名空间