資料結構面試之二——雙向鏈表表、迴圈鏈表、有序鏈表的常見操作
題註:《面試寶典》有相關習題,但思路相對不清晰,排版有錯誤,作者對此參考相關書籍和自己觀點進行了重寫,供大家參考。
二、雙向鏈表
雙向鏈表的建立是在單鏈表的基礎上,多了一個指向前驅的指標back。其他的操作類似,注意點就是在雙向鏈表的操作,尤其插入、刪除操作中需要修改兩個指標的指向,一個是back指標,一個是next指標。
1.雙向鏈表的構建【前面插入】
構建雙向鏈表注意點:1)修改first指標(頭指標)的指向。2)修改back、next指標。
//反向表頭插入,從前面插入...
template<typename Type>nodeType<Type>*doublyLinkedList<Type>::buildListBackward(){ nodeType<Type> *newNode; int num; cout << " Enter a list of integer end with -999." << endl; cin >> num; while(num != -999) { //..add newNode = new nodeType<Type>; newNode->info = num; newNode->next = NULL; newNode->back = NULL; if(first == NULL) { first = newNode; } else { newNode->next = first; first->back = newNode; first = newNode; } cin >> num; } return first;}
2.雙向鏈表的插入節點【此處考慮插入後保證有序,直接插入排序】
節點的插入同單鏈表,依然需要考慮多種因素。分以下幾類:
1)雙向鏈表為空白,“提示資訊”並返回。
2)雙向鏈表非空,待插入節點的元素值小於first節點,需要修改first指標。
3)雙向鏈表非空,待插入節點的元素值為鏈表中間的節點,需修改back、next指向。
4)雙向鏈表非空,待插入節點在最末尾節點以後,修改最末節點的指標。
//雙向不迴圈鏈表template<typename Type>voiddoublyLinkedList<Type>::insertNode(const Type& insertItem) //new{nodeType<Type> *current;nodeType<Type> *trailCurrent;nodeType<Type> *newNode;bool found = false; newNode = new nodeType<Type>;newNode->info = insertItem;newNode->next = NULL;newNode->back = NULL; //case 1: 空表if(first == NULL){ first = newNode;}else{ current = first; while( current != NULL) { if( current->info >= insertItem ) { found = true; break; } else { trailCurrent = current; current = current->next; } } //case 2: 第一個節點 if(current == first) { current->next = newNode; newNode->back = current; } else { //case 3: 中間節點 if(current != NULL) { newNode->next = trailCurrent->next; current->back = newNode; trailCurrent->next = newNode; newNode->back = trailCurrent; } else //case 4:最後一個節點前 { trailCurrent->next = newNode; newNode->back = trailCurrent; }//end case 4 }}}
3.雙向鏈表的刪除節點
同樣需要考慮以下幾點:
1) 鏈表為空白,“提示資訊”並返回。
2) 鏈表非空,尋找刪除的元素在鏈表是否存在。是的話,found=true;否的話,found=false。
3) 如果沒有找到包含尋找元素的節點,“錯誤提示”並返回。
4) 如果找到,主要節點位置。如果是前端節點或末尾節點主要修改first指標,及節點的back、next指向;如果不是前端節點或者末尾節點就是中間節點,主要back、next的指向,完成插入。
template<typename Type>void doublyLinkedList<Type>::deleteNode(constType& deleteItem) //new{ nodeType<Type> *current = new nodeType<Type>; nodeType<Type> *trailCurrent; bool found = false; //case 1: 空表 if(first == NULL) { cout << "The List is NULL!" << endl; exit(1); } else { current = first; while( current != NULL) { if( current->info == deleteItem ) { found = true; break; } else { trailCurrent = current; current = current->next; } } if(found) { //case 2: 第一個節點 if(current == first) { first = current->next; delete current; } else { //case 3: 中間節點 if(current != NULL) { if(current->next != NULL) //case3.1:要刪除的是中間節點. { trailCurrent->next =current->next; current->next->back = trailCurrent; delete current; } else { trailCurrent->next = NULL;//case3.2:要刪除的為最後一個節點. delete current; } } }// end else }// end if else { cout << "The elem " <<deleteItem << " is not Exist in the List!" << endl;//case 4: 鏈表中無此節點. } }//end else}//end
三、迴圈鏈表
迴圈鏈表能保障從每一個節點出發都能檢索鏈表。即:last指標的指向不再為空白,而是指向了first(頭結點)。
迴圈鏈表在構建鏈表、尋找元素、插入、刪除、操作中要注意修改末尾節點指標的指向,保證鏈表的迴圈。
1. 前插式構建迴圈鏈表
【思路】:每次在first指標前面插入節點;插入每個節點後注意修改last->link的指向。
template<typename Type>void cycleList<Type>::bulidCycListBackward() //前插構造迴圈鏈表.{ Type newItem; while(cin >> newItem, newItem != -999) { nodeType<Type>* newNode = newnodeType<Type>; newNode->info = newItem; newNode->link = NULL; if(first == NULL) { first = newNode; last = newNode; first->link = first; last->link = first; } else { newNode->link = first; last->link = newNode; first = newNode; } } cout << "輸入完畢!" << endl;}
2. 迴圈鏈表插入元素[後插入]
【思路】:同構造迴圈鏈表,每次在last指標後面插入節點;插入每個節點後注意修改last->link的指向。
//只在last末尾插入template<typename Type>voidcycleList<Type>::insertCycList(const Type& newItem){nodeType<Type> *newNode = new nodeType<Type>;newNode->info = newItem;newNode->link = NULL; if(first == NULL) //鏈表為空白...{ first = newNode; last = newNode; first->link = first; last->link = first;}else //鏈表非空...{ last->link = newNode; newNode->link = first; last = newNode;}cout << newItem << "was inserted!" <<endl;}
3. 刪除迴圈鏈表節點
考慮到是否為空白鏈表、刪除元素在鏈表中不存在、及節點存在(節點的位置可能為共分頭、中間、尾),所以分為以下5種情況分別處理。:
//case1:鏈表為空白。//case2:鏈表非空,刪除節點為前端節點。//case3:鏈表非空,刪除節點為尾節點。//case4:鏈表非空,刪除節點為中間節點。//case5:鏈表非空,不存在=deleteItem的節點。template<typename Type>voidcycleList<Type>::delCycList(const Type& deleteItem){nodeType<Type> *current = new nodeType<Type>;nodeType<Type> *trailCurrent = new nodeType<Type>;nodeType<Type> *temp ;bool found = false; if( first == NULL ){ cout << "The List is Empty!" << endl;//case1 return;}if( first->info == deleteItem ) //case2{ temp = new nodeType<Type>; temp = first; first = first->link; last->link = first; cout << "The node has" << deleteItem<< " was deleted! " << endl; delete temp; return;}else //cas3,case4.需要尋找後定位。{ current = first->link; while( !found && current != first) { if( current->info == deleteItem ) { found = true; break; } else { trailCurrent = current; current = current->link; } }// end while if(found) { temp = new nodeType<Type>; if(current == last) //case3 { temp = last; trailCurrent->link = last->link; //last->link = first last = trailCurrent; } else { temp = current; trailCurrent->link= current->link; } cout << "The Elem : " <<deleteItem << " was deleted! " << endl; delete temp; } else { cout << "The Elem : " <<deleteItem << " was not Exist in the List! " << endl; }}//end else}
四、有序鏈表
注意:此處有序鏈表無非是在之前的一、單鏈表的操作的基礎上,在插入元素的時候,按順序插入,考慮的排序。
為了體現有序的特點,特通過遞迴實現了有序鏈表的逆序列印。關於有序鏈表或者鏈表的非遞迴實現,可以通過棧實現。下一節會分析並實現。
1. 有序鏈表的插入
考慮以下三種情況:
1) 當前鏈表為空白;
2) 當前鏈表非空,要插入的元素值小於頭結點的元素值;
3) 當前鏈表非空,要插入的元素值大於頭結點的元素值,可以考慮找到第一個大於其的元素則停止搜尋,插入其前即可。
template<typename Type>void orderedLinkedListType<Type>::insertNode(constType& newItem){nodeType<Type>* current;nodeType<Type>* trailCurrent;nodeType<Type>* newNode; bool found = false; newNode = new nodeType<Type>;newNode->info = newItem;newNode->link = NULL; if(first == NULL) //case1:鏈表為空白.{ first = newNode; last = first;}else{ current = first; while( !found && current != NULL) //迴圈尋找 { if(current->info >= newItem) { found = true; break; } else { trailCurrent = current; current = current->link; } }//end while //first 特殊處理.. if(current == first) //case2:新插入的元素小於first節點的元素值.. { newNode->link = first; first = newNode; } else //其他.. { newNode->link = current; //case3:新插入的節點在非first位置 trailCurrent->link = newNode; }}//end else}
2. 遞迴實現有序鏈表的逆序列印
template<typename Type>voidorderedLinkedListType<Type>::printListReverse() const //逆序列印.{ reversePrint(first); cout << endl;} //遞迴實現單鏈表的逆序列印.template<typename Type>voidorderedLinkedListType<Type>::reversePrint(nodeType<Type>* current)const //逆序列印{if(current != NULL){ reversePrint(current->link); cout << current->info << "\t";}}