上一篇我們已經實現過了線性表的順序存儲結構:順序表,現在我們繼續來實現基本的線性表的線性存儲結構之單鏈表;
首先,單鏈表的存儲結構是以節點數據爲基本存儲單元的(一個節點存儲一個數據,頭結點不存放數據),其次,其內存是向堆中申請的,而不是向棧中申請的,所以單鏈表的長度是可拓展的。所以其實現方式與上一次順序表的實現方式大有不同。下面請看:
首先,我們的先實現存儲數據的節點(爲適應數據的不同類型,使用了類模板機制):
#pragma once //避免被包含多次
template<typename T>
class Node /*與書中不同,我這裏並沒有採用結構體類型,而是採用了類,但實際上功能是一樣的*/
{
public:
T data; //數據存儲域
Node *next; //指向下一個節點的指針
};
實現了單鏈表的數據存放節點之後,我們再來實現單鏈表的類模板實現
#pragma once /*避免文件被include多次效果與 #ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
#endif 相同*/
#include<iostream> //頭文件的引入
#include"Node.h";
using namespace std;
template<typename T> /*類模板的聲明,成員函數中實現了基本的增刪查改,數據成員中有數據的頭結點,和計算鏈表長度的int類型的Length成員(這個在書中並沒有)*/
class LinkList
{
public:
LinkList(T a[], int n); //有參構造函數 使用了頭插法
~LinkList(); //析構函數
int Length(); //返回單鏈表的長度
T Get(int i); //按位查找,查找第i個節點的元素
int Locate(T x); //按值查找,查找鏈表中第一個值爲x的元素,並返回序號
bool Insert(int i, T x); //插入元素,在第i個位置插入值x
bool Delete(int i); //刪除節點,刪除第i個節點
bool InsertHead(T x); //頭插法插入節點
bool InsertTail(T x); //尾插法插入節點
void ListTraverse(); //遍歷節點
private:
Node<T> *first; //頭結點的指針
int m_Length; //實際使用過程當中,添加多一個數據成員Length會更好
};
**以下是成員函數的實現**
template<typename T>
LinkList<T>::LinkList(T a[], int n) //有參構造函數, 使用頭插法(注意點:頭插法是將元素放在頭結點的後面)
{
first = new Node<T>; //空鏈表的初始化
first->next = NULL;
for (int i=0;i<n;i++) //有疑問:這樣做,頭結點是不是沒數據? 經過調試,發現頭結點是不存儲數據的
{
Node<T> *s = new Node<T>;
s->data = a[i];
s->next = first->next;
first->next = s;
}
m_Length = n;
/*for (int i = 0; i < n; i++) //直接調用下面的頭插法插入數據更方便!
{
InsertHead(a[i]);
}*/
}
//template<typename T>
//LinkList::LinkList(T a[], int n) //同樣是有參構造函數,但是使用的是尾插法
//{
// Node<T> *first = new NOde<T>;
// Node<T> *r = first; //將頭指針賦值給變量r
// for (int i = 0; i < n; i++)
// {
// s = new Node;
// s->data = a[i]; //創建新節點,賦值
// r->next = s; //將r的指針指向s
// r = s; //變量r後移到最後一個節點
// }
// r->next = NULL; //將尾節點的指針置空
//}
template<typename T>
bool LinkList<T>::InsertHead(T x) //頭插發插入數據
{
Node<T> *Temp = first->next; //建立指向頭指針的臨時變量
Node<T> *s = new Node<T>; //建立新節點
if (s==NULL) //判斷新節點是否申請成功
{
return false;
}
s->data = x; //賦值
s->next = Temp; //上鍊
first->next = s;
m_Length++; //鏈表的長度加一
return true; //插入成功,返回true
}
template<typename T>
bool LinkList<T>::InsertTail(T x) //使用尾插法插入數據,使數據插入到最後一個
{
Node<T> *p = first; //建立臨時遍歷指針
Node<T> *s = new Node<T>; //建立新節點
if (s == NULL) //判斷新節點是否申請成功,若申請失敗,則退出函數,不插入數據
{
return false;
}
s->data = x;
while (p->next != NULL) //遍歷指針,使臨時指針指向尾節點
{
p = p->next;
}
p->next = s; //尾節點重新導向,將新節點上鍊
s->next = NULL; //將上鍊後的尾節點的指針域指向空
m_Length++;
return true; //返回true,插入成功
}
template<typename T>
LinkList<T>::~LinkList() //析構函數
{
while (first!=NULL)
{
Node<T> *q = first; //遍歷刪除頭指針指向的節點,將頭指針暫存
first = first->next; //將頭指針後移
delete q; //從鏈表中脫離出來的指針刪除,釋放內存
}
m_Length = 0;
}
template<typename T>
int LinkList<T>::Length() /*返回鏈表的長度的算法,實現思想:設定循環函數,將節點的指針從頭指針開始依次後移,
後移一次便將計數器自加1,直至到尾節點的指針爲空,此時結束循環,返回計數器*/
{
/*int num=0;
Node<T> *p = first->next;
while (p!= NULL)
{
p = p->next;
num++;
}*/
return m_Length; //添加數據成員length後,使得返回鏈表的長度函數更簡單,代碼更少
/*return num;*/
}
template<typename T>
T LinkList<T>::Get(int i) //按位查找,返回第i個節點的元素
{
Node<T> *p = first->next;
int count = 1;
while (p!= NULL&&count < i)
{
p = p->next;
count++;
}
if (p == NULL)
{
throw"位置";
}
else
{
return p->data;
}
}
template<typename T>
int LinkList<T>::Locate(T x) //按值查找,返回d第一個匹配值的序號
{
Node<T> *p = first->next;
int count = 1;
while (p != NULL)
{
if (p->data == x)
{
return count;
}
p = p->next;
count++;
}
return 0;
}
template<typename T>
bool LinkList<T>::Insert(int i,T x) //往鏈表中插入元素,i爲要插入的位置,x爲要插入的值
{
Node<T> *p = first;
int count = 0;
int num = i - 1;
while (p!= NULL&&count <num)
{
p = p->next;
count++;
}
if (p == NULL)
{
return false;
}
else
{
Node<T> *s = new Node<T>;
s->data = x;
s->next = p->next;
p->next = s;
m_Length++;
return true;
}
}
template<typename T>
void LinkList<T>::ListTraverse()
{
Node<T> *p = first->next;
while (p != NULL)
{
cout << p->data<<",";
p = p->next; //遍歷的指針的後移,注意不能寫成p++,因爲這是節點
}
}
template<typename T>
bool LinkList<T>::Delete(int i)
{
Node<T> *p = first;
int count = 0;
while (p != NULL&&count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL)
{
return false;
}
else
{
Node<T> *q;
q = p->next;
p->next = q->next;
delete q;
m_Length--;
return true;
}
}
以上就是單鏈表類模板的實現。
需要注意的幾個點:
- 頭結點並不實際存放實際的數據,所以在實現個別成員函數(如插入指定位置的函數,查找指定值所在位置的函數,刪除函數)的時候需要特別注意,頭節點並不是第一個數據節點,而是,頭結點的下一個節點,所以,實現的時候需要注意一下,比如,計算位置的num數該從何時開始計數?該從何時開始循環?循環何時結束?
- 指針的移動,不是p++,而是p->next
- 節點數據中存放的指針指向的是下一個一整塊的節點,而不僅僅是下一個節點的存儲數據的域、
- 時間複雜度方面:插入和刪除時無需移動元素時間複雜度爲o(1) (優點) ,而按位查找方面則不如順序表方便。時間函數爲o(n),所以解決問題時根據需求選擇存儲結構
- 刪除和析構函數方面:由於這是向堆中申請的內存,所以不能單純地摘鏈,還要將申請到地空間釋放出來
實例調用:
#include<iostream>
#include"LinkList.h"
using namespace std;
int main()
{
int a[5] = {1,2,3,4,5};
LinkList<int> MyList(a,5);
cout << MyList.Length() << endl; //鏈表長度函數調用成功
cout << "第5個節點的元素爲:" << MyList.Get(5) << endl; //輸入有效長度的數據可成功調用,但是,若輸入不合法的數據將出錯,throw的語句未完成
if (MyList.Locate(5) != 0)
{
cout << "元素5所在的位置爲:" << MyList.Locate(5) << endl;
} //輸入合法的數據,測試成功
else if(MyList.Locate(5)==0){
cout << "輸入數據不合法,輸入的節點位置超過鏈表超度" << endl;
}
if (MyList.Insert(3, 2))
{
cout << "插入元素成功!" << endl;
}//插入元素測試成功
else
{
cout << "插入元素失敗!" << endl;
}
MyList.ListTraverse();
if(MyList.Delete(3))
{
cout << "刪除節點成功" << endl;
}
else
{
cout << "刪除節點失敗" << endl;
}
MyList.ListTraverse(); //單鏈表的遍歷成功
cout << endl;
MyList.InsertHead(7); //調用頭插法
cout << "調用頭插法成功!";
MyList.ListTraverse();
MyList.InsertTail(4); //調用尾插法
cout << endl;
cout << "調用尾插法成功!";
MyList.ListTraverse();
return 0;
}
調用結果:
最後一點:
這只是單鏈表地基本實現方式,要想用來解決實際問題,還要經過添加各種算法(函數)。
以上!