最简单的操作无非是以下几点:create search insert delete
1.创建一个列表并且遍历它,打印出各节点的值
struct node{ int data; node* next; }; node* init_node(int value) { node* head = new node(); head->data = value; head->next = NULL; return head; } void create_list(node* head,int value) { node* current_node = head; node* new_node = init_node(value); while(current_node->next!=NULL) { current_node = current_node->next; } current_node->next = new_node; } node* init_list(int* arr,int len) { node* head = init_node(0); for(int i=0;i<len;i++) { create_list(head,arr[i]); } return head; } void list_traversal(node* head) { node* current_node = head; while(current_node->next!=NULL) { current_node = current_node->next; cout<<current_node->data<<" "; } }
关于初始化过程,不要放在main函数中进行。
2.查找一个结点
void search_node(node* head,int value) { node* current_node = head; while(current_node->data!=value && current_node->next!=NULL){ current_node = current_node->next; } if(current_node->data==value){ cout<<endl<<"search node data is "<<current_node->data<<endl; }else{ cout<<endl<<value<<" is not in the list"<<endl; } }
3.插入结点
当插入一个结点时,可以选择插入到当前结点的前面或者后面。
void insert_node_beforeVar(node* head,int var,int value) { node* current_node = head; node* pre_node; node* new_node = init_node(value); while(current_node->data!=var && current_node->next!=NULL){ pre_node = current_node; current_node = current_node->next; } if(current_node->data==var){ pre_node->next = new_node; new_node->next = current_node; } } void insert_node_behindVar(node* head,int var,int value) { node* current_node = head; node* new_node = init_node(value); while(current_node->data!=var && current_node->next!=NULL){ current_node = current_node->next; } if(current_node->data==var){ node* next_node = current_node->next; current_node->next = new_node; new_node->next = next_node; } }
4.删除一个结点
void delete_node(node* head,int value) { node* current_node = head; node* pre_node; while(current_node->data!=value && current_node->next!=NULL){ pre_node = current_node; current_node = current_node->next; } if(current_node->data==value){ node* next_node = current_node->next; //删除结点导致链表断开,因此要保存下一个结点 pre_node->next = next_node; delete(current_node); } }
下面总结下关于链表的各种算法,都是在平时做题时遇到的,参考了网上很多DS的杰作,在此感谢各位DS。
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_node = 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
换个思路,柳暗花明。