數據結構之鏈表-鏈表實現及常用操作(C++篇)


數據結構之鏈表-鏈表實現及常用操作(C++篇)

0.摘要

  • 定義
  • 插入節點(單向鏈表)
  • 刪除節點(單向鏈表)
  • 反向遍歷鏈表
  • 找出中間節點
  • 找出倒數第k個節點
  • 翻轉鏈表
  • 判斷兩個鏈表是否相交,並返回相交點
  • 判斷鏈表是否有環路,獲取連接點,計算環的長度
  • 二叉樹和雙向鏈表轉化

1.定義

1.1單向鏈表

單向鏈表的節點包括:

  1. 數據域:用於存儲數據元素的值。

  2. 指針域(鏈域):用於存儲下一個結點地址或者說指向其直接后繼結點的指針。

     struct Node{
     	int value;
     	Node * next;
     };
    

1.2雙向鏈表

雙向鏈表的節點包括:

  1. 數據域:用於存儲數據元素的值。

  2. 左指針域(左鏈域):用於存儲上一個結點地址或者說指向其直接前繼結點的指針。

  3. 右指針域(右鏈域):用於存儲下一個結點地址或者說指向其直接后繼結點的指針。

     struct DNode{
     	int value;
     	DNode * left;
     	DNode * right;
     };
    

2.常用操作例題

2.1插入節點(單向鏈表)

//p節點后插入值為i的節點
void insertNode(Node *p, int i){
	Node* node = new Node;
	node->value = i;
	node->next = p->next;
	p->next = node;
}

2.2刪除節點(單向鏈表)

當需要刪除一個節點p時,只需要將p的前一個節點的next賦為p->next,但是由於是單向的,只知道p,無法知道p前一個節點,所以需要轉換思路。將p下一個的節點覆蓋到p節點即可,這樣就等效於將p刪除了。

void deleteNode(Node *p){
	p->value = p->next->value;
	p->next = p->next->next;
}

2.3反向遍歷鏈表

法一:反向遍歷鏈表就類似於事先遍歷的節點后輸出,即“先進后出”,那么可以將鏈表遍歷存放於棧中,其后遍歷棧依次彈出棧節點,達到反向遍歷效果。

//1.stack
void printLinkedListReversinglyByStack(Node *head){
	stack<Node* > nodesStack;
	Node* pNode = head;
	//遍歷鏈表
	while (pNode != NULL) {
	    nodesStack.push(pNode);
    	pNode = pNode->next;
	}
	while (!nodesStack.empty()) {
    	pNode=nodesStack.top();
    	printf("%d\t", pNode->value);
    	nodesStack.pop();
	}
}
//2.遞歸
void printLinkedListReversinglyRecursively(Node *head){
	if (head!=NULL) {
    	if (head->next!=NULL) {
        	printLinkedListReversinglyRecursively(head->next);
    	}
    	printf("%d\t", head->value);
	}
}

2.4找出中間節點

用slow和fast指針標記,slow每次走一步,fast每次走兩步,當fast到尾節點時,slow就相當於總長度的一半,即在中間節點。

//找出中間節點
Node* findMidNode(Node* head){
	Node* slow = head;
	Node* fast = head;
	while (fast->next != 0&&fast->next->next!=0) {
    	slow = slow->next;
    	fast = fast->next->next;
	}
	return slow;
}

2.5找出倒數第k個節點

用slow和fast指針標記,fast指針事先走k步,然后slow和fast同時走,當fast到達末節點時,slow在fast的前k個節點,即為倒數第k個節點。

//找出倒數第k個節點
Node* findKNode(Node* head,int k){
	Node *temp1 = head;
	Node *temp2 = head;
	while (k-->0) {
    	if(temp2 == NULL){
        	return NULL;
		}
    	temp2 =temp2->next;
	}
	while (temp2->next != NULL&&temp2->next->next!=NULL) {
    	temp1 = temp1->next;
    	temp2 = temp2->next->next;
	}
	return temp1;
}

2.6翻轉鏈表

題意即為將鏈表反過來,即,原本為p1-p2-p3翻轉為p3-p2-p1。讀者需自行畫圖體會指針操作。

//翻轉鏈表
Node * reverseLinkedList(Node* head,int k){
	Node *reversedHead = NULL;
	Node *pNode = head;
	Node *pre = NULL;
	while (pNode!=NULL) {
    	if (pNode->next==NULL) {
        	reversedHead = pNode;
    	}
    	Node* nxt = pNode->next;
    	pNode->next = pre;
    	pre=pNode;
    	pNode=nxt;
    }
	return reversedHead;
}

2.7判斷兩個鏈表是否相交,並返回相交點

如果兩個鏈表相交,其形狀必為y形,而不可以能為x形,即兩條鏈表必有相同的尾節點。首先,計算得到兩個鏈表的長度:m,n,求得兩個鏈表長度之差distance=|m-n|,讓較長得那個鏈表事先走distance步,這樣,若是鏈表相交得話,二者指針必相撞,相撞點即為相交點。

Node* findCrosspoint(Node* l1, Node* l2){
	int m = getLinkedListLength(l1);
	int n = getLinkedListLength(l2);
	int distance=0;
	Node *temp1= l1;
	Node *temp2= l2;
	if (m>n) {
    	distance = m - n;
    	while (distance>0) {
        	distance--;
        	temp1=temp1->next;
    	}
	} else{
    	distance = n - m;
    	while (distance>0) {
        	distance--;
        	temp2 = temp2->next;
    	}
	}
	while(temp1!=temp2&&temp1->next!=NULL&&temp2->next!=NULL){
    	temp1=temp1->next;
    	temp2=temp2->next;
	}
	if(temp1 == temp2){
    	return temp1;
	}
	return 0;
}

2.8判斷鏈表是否有環路,獲取連接點,計算環的長度

此題很有意思,具體詳細請參考:http://www.cnblogs.com/xudong-bupt/p/3667729.html
判斷是否含有環:slow和fast,slow指針每次走一步,fast指針每次走兩步,若是鏈表有環,fast必能追上slow(相撞),若fast走到NULL,則不含有環。

//判斷是否含有環
bool containLoop(Node* head){
	if (head==NULL) {
    	return false;
	}
	Node* slow = head;
	Node* fast = head;
	while (slow!=fast&&fast->next!=NULL) {
    	slow = slow->next;
    	fast = fast->next->next;
	}
	if (fast==NULL) {
    	return false;
	}
	return true;
}

判斷環的長度:在相撞點處,slow和fast繼續走,當再次相撞時,slow走了length步,fast走了2*length步,length即為環得長度。

//獲得環的長度
int getLoopLength(Node* head){
	if (head==NULL) {
    	return 0;
	}
	Node* slow = head;
	Node* fast = head;
	while (slow!=fast&&fast->next!=NULL) {
    	slow = slow->next;
    	fast = fast->next->next;
	}
	if (fast==NULL) {
    	return 0;
	}
	//slow和fast首次相遇后,slow和fast繼續走
	//再次相遇時,即slow走了一圈,fast走了兩圈
	int length = 0;
	while (slow!=fast) {
    	length++;
    	slow = slow->next;
    	fast = fast->next->next;
	}
	return length;
}

環得連接點:slow和fast第一次碰撞點到環的連接點的距離=頭指針到環的連接點的距離,此式可以證明,詳見上面鏈接。

//獲得環的連接點
Node* getJoinpoit(Node* head){
	if (head==NULL) {
    	return NULL;
	}
	Node* slow = head;
	Node* fast = head;
	while (slow!=fast&&fast->next!=NULL) {
    	slow = slow->next;
    	fast = fast->next->next;
	}
	if (fast==NULL) {
    	return NULL;
	}
	Node* fromhead = head;
	Node* fromcrashpoint = slow;

	while (fromcrashpoint!=fromhead) {
    	fromhead = fromhead->next;
    	fromcrashpoint = fromcrashpoint->next;
	}
	return fromhead;
}

2.9二叉樹和雙向鏈表轉化

二叉樹和雙向鏈表轉化指的是,二叉樹節點結構和雙向鏈表的結構想類似,只不過二叉樹節點的節點存儲的兩個指針為左右子數,而雙向鏈表存儲的是前后節點。題意為將二叉樹的某種遍歷轉化為鏈表存儲。此題很明顯該用遞歸,讀者可以畫圖體會一下指針變化。

二叉樹節點或雙向鏈表節點定義:

struct BinaryTreeNode{
	int value;
	BinaryTreeNode* left;
	BinaryTreeNode* right;
};

二叉樹的中序遍歷轉換為雙向鏈表

BinaryTreeNode* convertNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInLast){
	if (pNode == NULL) {
    	return NULL;
	}
   	BinaryTreeNode *pCurrent = pNode;
	if (pCurrent->left != NULL) {
    	convertNode(pCurrent->left, pLastNodeInLast);
	}

	pCurrent->left = *pLastNodeInLast;
	if (*pLastNodeInLast != NULL) {
    	(*pLastNodeInLast)->right=pCurrent;
	}

	*pLastNodeInLast = pCurrent;
	if (pCurrent->right != NULL) {
    	convertNode(pCurrent->right, pLastNodeInLast);
	}
	return NULL;
}

BinaryTreeNode* convertBTToDLL(BinaryTreeNode* root){
	BinaryTreeNode *pLastNodeInLast = NULL;
	convertNode(root, &pLastNodeInLast);
	BinaryTreeNode *pHeadOfList = pLastNodeInLast;
	while (pHeadOfList != NULL && pHeadOfList->left != NULL) {
    	pHeadOfList = pHeadOfList->left;
	}
	return pHeadOfList;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM