最簡單的操作無非是以下幾點: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
換個思路,柳暗花明。