單鏈表 C++ 實現 - 含虛擬頭節點


 

本文例程下載鏈接:ListDemo

 

鏈表 vs 數組

鏈表和數組的最大區別在於鏈表不支持隨機訪問,不能像數組那樣對任意一個(索引)位置的元素進行訪問,而需要從頭節點開始,一個一個往后訪問直到查找到目標位置。

 

單鏈表

與順序表相對,鏈表是一種鏈式存儲方式。單鏈表是實現順序表最簡單的一種鏈表,根據是否包含虛擬頭節點,分為含虛擬頭節點和不含虛擬頭節點兩種方式。本文以含虛擬頭節點為例,用C++實現單鏈表數據結構ADT。

節點:鏈表的組成元素,每個節點包含數據域data和指針域next。例程為了簡化設計,data用int類型表示。

虛擬頭節點:鏈表的第一個節點,為了維護鏈表操作方便,但不存放數據。

 

虛擬頭節點(dummy head) VS 無虛擬頭節點

含虛擬頭節點的鏈表優勢:所有的數據節點(除去頭節點)都是對等的,對鏈表節點的API操作一般不影響頭節點本身指針變化(除去頭節點next域)。

缺點:需要額外考慮頭節點影響,比如IsEmpty()判斷條件, 和Length()是否對等(IsEmpty <=> length==0?),頭節點對用戶是否可見,能否被用戶直接remove等。

 

單鏈表結構設計

含虛擬頭節點的單鏈表list如下圖所示,包含一個虛擬頭節點V0,而V1~Vn通過前驅節點next域進行鏈接,從而形成一個單向鏈式結構。

虛擬頭節點V0,不包含數據;V1~Vn包含數據,Vn->next = NULL

名稱 描述 代表自定義符號
虛擬頭節點 不包含數據 Vh/head
尾節點 鏈表的最后一個節點,特征:next=NULL Vn-1/tail
普通數據節點 包含具有實際意義數據的節點 V0~Vn-1
長度 包含實際意義數據節點數為n(不包括Vh) length
空鏈表 虛擬頭節點為空,即head=NULL V0=NULL
位置

從V0開始(Vh后繼節點)開始計數0,直到Vn-1(尾節點)的節點對應位置。范圍[0,n-1]

position
插入節點 在指定位置處插入節點,除插入節點及前驅和后繼,不改變鏈表其他節點關系 insert
刪除節點 在指定位置處刪除節點, 除插入節點及前驅和后繼,不改變鏈表其他節點關系 remove
     

 

 

 

 

單鏈表ADT設計

1. 鏈表節點ADT Node.h

/**
 * 單鏈表節點類
 */
class Node
{
	int data;
	Node *next;

public:
	Node();
	Node(Node*, int);
};

 

2. 單鏈表ADT LinkedList.h

/**
 * 單鏈表類
 * @description 帶虛擬頭節點
 */
class LinkedList
{
private:
	Node *head;
	Node *tail;

public:
	LinkedList();
	virtual ~LinkedList();
	Status Init();          // 初始化鏈表
	Status Destroy();       // 銷毀鏈表

	int Length();           // 求鏈表長度(有效節點)節點個數
	bool IsEmpty();         // 鏈表是否為空
	Status Insert(int pos, int value);  // 在指定位置生產節點, 並插入數據
	Status Remove(int pos);  // 刪除指定位置節點
	int GetValue(int pos);   // 讀取指定位置節點數據
	Node* GetAddress(int pos); // 獲取知道位置節點的地址
	int SearchPosition(int value); // 搜索第一個出現的元素值的節點位置
	Status Update(int pos, int value); // 更新指定位置節點數據
	Status ClearList(); // 清除鏈表數據(不包含頭節點)
	Status PrintList(); // 順序打印鏈表節點數據
	Status Reverse();   // 反轉鏈表
};

 

3. Node實現 Node.cpp

#include "Node.h"
#include <cstdlib>

Node::Node() {
	data = 0;
	next = NULL;
}

Node::Node(Node* newNext, int newValue)
{
	data = newValue;
	next = newNext;
}

Node::~Node()
{
	// TODO Auto-generated destructor stub
}

 

4. 鏈表實現LinkedList.cpp

#include "LinkedList.h"
#include <cstdlib>
#include <iostream>

using namespace std;

LinkedList::LinkedList() {
	head = NULL;
	tail = NULL;
}

LinkedList::~LinkedList() {
	Destroy();
}

/**
 * 初始化鏈表
 */
Status LinkedList::Init()
{
	// 創建虛擬頭節點
	head = new Node(NULL, 0);

	if(head != NULL)
	{
		return OK;
	}

	return ERROR;
}

/**
 * 銷毀鏈表
 * @description 與初始化操作相對, 刪除所有鏈表節點, 包括頭節點
 */
Status LinkedList::Destroy()
{
	if(head)
	{
		Node *curP = head;

		while(head)
		{
			curP = head->next;
			delete head;
			head = curP;
		}

		return OK;
	}
	else
	{
		cout <<"The list is NULL"<<endl;
				return ERROR;
	}
}

/**
 * 求鏈表長度(有效節點)節點個數
 * @description 從虛擬頭節點的下一個節點開始計算有效節點數
 */
int LinkedList::Length()
{
	if(!head)
	{
		cout <<"The list is NULL"<<endl;
		exit(-1);
	}

	Node *curP = head->next;
	int n = 0;
	while(curP != NULL)
	{
		n ++;
		curP = curP->next;
	}

	return n;
}

/**
 * 判斷鏈表是否為空
 * @description 判斷依據: 虛擬頭節點head == NULL
 * 注: 長度為0 不代表鏈表為空
 */
bool LinkedList::IsEmpty()
{
	return (head == NULL);
}

/**
 * 在指定位置生產節點, 並插入數據
 * @param pos [in] 待插入位置. 從頭節點的后繼開始為0計數,所需要經過的節點數。范圍:0~n-1
 * @param value [in] 待插入節點數據域
 */
Status LinkedList::Insert(int pos, int value)
{
	if(IsEmpty())
	{
		cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
		exit(-1);
	}

	// create a new Node
	Node *newNode = new Node(NULL, value);

	if(pos == 0)
	{
		newNode->next = head->next;
		head->next = newNode;
	}
	else if(pos >0 && pos <= Length())
	{
		// find the predecessor node to be inserted
		Node *p = GetAddress(pos-1);

		// insert the new Node to List
		if(p != NULL)
		{
			if(p->next != NULL)
			{// not tail
				newNode->next  = p->next;
			}
			p->next = newNode;
		}
	}
	else
	{
		cout<<"Error: Input param Pos is illegal(<0)"<<endl;
		return ERROR;

	}

	return OK;
}

/**
 * 刪除指定位置節點
 * @param pos [in] 待插入位置. pos有效范圍: [0, n), 0代表虛擬節點后面一個節點, 虛擬節點無法通過此API刪除
 *
 */
Status LinkedList::Remove(int pos)
{
	Node *preNode = NULL;
	Node *curNode = NULL;
	Node *nextNode = NULL;

	if(pos < 0 || pos >= Length())
	{
		cout << "Remove node with error position"<<endl;
		exit (-1);
	}
	else if(pos == 0)
	{// find the prior node, namely , head node
		preNode = head;
	}
	else if(pos < this->Length())
	{
		// find the prior node
		preNode = GetAddress(pos - 1);
	}

	if(!preNode)
	{
		return ERROR;
	}

	curNode = preNode->next;
	if(curNode)
	{
		nextNode = curNode->next;
		preNode->next = nextNode;
		delete curNode;
	}

	return OK;
}

/**
 * 讀取指定位置節點數據
 */
int LinkedList::GetValue(int pos)
{
	// find the  node
	Node *curP = GetAddress(pos);
	if(curP != NULL)
	{
		return curP->data;
	}
	else
	{
		return 0;
	}
}

/**
 * 獲取位置節點的地址
 * @param pos [in] 從頭節點的后繼開始為0計數,所需要經過的節點數。范圍:[0, n-1], n是鏈表長度(有效數據節點數)
 * @return 節點的地址
 */
Node* LinkedList::GetAddress(int pos)
{
	// valid list is null or not
	if(!head)
	{
		cout <<"The list is NULL"<<endl;
		exit(-1);
	}

	// valid intput param
	if(pos < 0 || pos >= Length())
	{
		cout <<"Insert position is out of the list's bounds"<<endl;
		exit(-1);
	}

	// 順序查找位置pos的節點
	int i = 0;
	Node *curP = head->next;
	while(curP != NULL)
	{
		if(i == pos)
		{
			return curP;
		}

		i ++;

		curP = curP->next;
	}

	return NULL;
}

/**
 * 搜索第一個出現的元素值的節點位置
 * @param value 待查找值
 * @return 鏈表第一個節點的數據域=value的位置
 * - ERROR 錯誤
 * - >=0 位置序號
 */
int LinkedList::SearchPosition(int value)
{
	// valid list is null or not
	if(!head)
	{
		cout <<"The list is NULL"<<endl;
		exit(-1);
	}

	// sequential search
	Node *p = head->next;
	int i = 0;
	while(p != NULL)
	{
		i ++;
		if(p->data == value)
			return i;
		else
			p = p->next;
	}

	cout<< "Can't find the value in list"<<endl;
	return ERROR;
}

/**
 * 更新指定位置節點數據
 * @param pos [in] 待更新節點位置
 * @param value [in] 待更新節點要修改的目標值
 * @return 更新結果
 * - OK 正常更新
 * - ERROR 沒有找到待更新節點, 可能由位置錯誤或者鏈表為空導致
 */
Status LinkedList::Update(int pos, int value)
{
	// find the node
	Node *curP = GetAddress(pos);
	if(curP != NULL)
	{
		curP->data = value;
		return OK;
	}

	return ERROR;
}

/**
 * 清除鏈表數據(不包含頭節點)
 */
Status LinkedList::ClearList()
{
	// valid list is null or not
	if(!head)
	{
		cout <<"The list is NULL"<<endl;
		exit(-1);
	}

	Node *p = head->next;
	Node *tmp = NULL;

	while(p!=NULL)
	{
		tmp = p;
		p = p->next;
		delete tmp;
	}

	head->next = NULL;

	return OK;
}

/**
 * 順序打印鏈表節點數據
 */
Status LinkedList::PrintList()
{
	// valid list is null or not
	if(IsEmpty())
	{
		cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
		return ERROR;
	}

	cout <<"List:[ ";
	if(Length() == 0)
	{
		cout<<"NULL";
	}
	else
	{
		Node *p = head->next;

		while(p != NULL)
		{
			cout<<p->data<<" ";
			p = p->next;
		}
	}
	cout <<"] "<<endl;
	return OK;
}

/**
 * 反轉鏈表, 除虛擬頭節點
 */
Status LinkedList::ReverseList()
{
	// valid list is null or not
	if(IsEmpty())
	{
		cout <<"The list is NULL. Pls Create an List and init it first."<<endl;
		exit(-1);
	}

	if(Length() == 0)
	{
		cout <<"The list length = 0. Pls insert new Node."<<endl;
		exit(-1);
	}

	// reverse List
	Node *pre = head->next; // precursor Node
	Node *cur = pre->next;  // current Node
	Node *nxt = NULL;       // successor Node

	while(cur != NULL)
	{
		nxt = cur->next;
		cur->next = pre;
		pre = cur;
		cur = nxt;
	}

	// set the tail Node
	head->next->next = NULL;

	// set the head Node's next field
	head->next = pre;

	return OK;
}

  

6. 測試結果

可以看到已經實現了鏈表基本的插入、打印、反轉等功能。

 

 


免責聲明!

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



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