從今天開始,我就開始了算法的學習。也將學習的東西分享給大家。有不足的地方也希望大家指出。
一. 鏈表簡介
- 一個班級能最大容納50人,如果用數組來儲存的話,就要定義一個大小爲50的數組來儲存。但是如果班級沒有50人呢?只有25人呢?這樣就會造成內存的浪費。又遇到班級擴張,最大人數擴張到100人呢?這樣又會造成內存不足。這是數組的兩大缺點。
- 爲了解決數組的不足,於是我們設計了一種新的數據結構–鏈表,鏈表可以動態的申請內存和動態的釋放內存。根據這兩大特性,正好完美的解決了數組的不足。
- 鏈表是通過一系列的節點(包括數據域和指向下一節點的指針域)依次鏈接而形成的一種數據結構。第一個節點稱爲頭結點,最後一個節點稱爲尾節點,一般在頭節點中我們不存儲數據
二. 單鏈表的實現
1. 節點的表示
下面直接通過c++的代碼演示
class Node {
public:
int data; //存放數據
Node* next; // 指向下一節點的指針
};
2. 節點的連接
- 假設tail指向鏈表的最後一個節點,L2是一個新的節點。鏈表不爲空
tail->next = L2;
L2->next = NULL;
tail = L2;
- 假設head,tail爲空指針,L2是一個新的節點。鏈表爲空
head->next = L2;
tail = L2;
L2->next = NULL:
3. 節點的遍歷
- 假設head爲鏈表的頭結點
p = head;
while(p)
{
//在這裏可以進行操作
p = p->next;
}
4. 節點的申請和刪除
//申請一個節點
Node p = new Node;
//刪除一個節點
delete p;
5. 鏈表的實現
在這裏將整個鏈表的相關功能實現一下。
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
};
class List {
private:
Node* head=NULL, *tail=NULL;
public:
List() {
}
List(int e1) {
Node *p;
p = new Node;
p->data = e1;
p->next = NULL;
head = p;
tail = p;
}
~List()
{
Node * p = head,*q;
while (p->next)
{
q = p->next;
delete p;
p = q;
}
delete p;
}
void get_info() {
Node *q = new Node;
q = head;
while (q)
{
cout << q->data<<" "; // 打印鏈表裏面的內容
q = q->next;
}
}
//插入節點
void addlistNode(int t) {
Node *q = new Node;
q->info = t;
if (head == NULL)
{
head = q;
tail = q;
q->next = NULL;
}
else
{
tail->next = q;
tail = q;
q->next = NULL;
}
}
//刪除節點
void delelistNode(int t)
{
Node* q = new Node;
Node* p = new Node;
q = head;
while (q && q->info != t)
{
p = q;
q = q->next;
}
if (q->info == t )
{
if (tail == q)
tail = p;
p->next = q->next;
delete q;
}
else
cout << "this int is not in list";
}
};
int main()
{
List a(5);
a.addlistNode(10);
a.addlistNode(20);
a.delelistNode(20);
a.addlistNode(10);
a.addlistNode(30);
a.delelistNode(10);
a.get_info();
return 0;
}
三. 雙鏈表的實現
1. 與單鏈表的不同
與單鏈表唯一不同的區別就是單鏈表的指針域不止有指向下一節點的指針,而且還有指向上一節點的指針。
單鏈表的指針只能向後移動,雙鏈表的指針可以前後移動,這樣就解決了單鏈表笨拙的特點,不夠靈活。
2. 節點的表示
class Node {
public:
int data; //存放數據
Node* next; // 指向下一節點的指針
Node* prev; //指向上一節點的指針
};
3. 節點的連接
- 當鏈表爲空時,head,tail爲指向節點的空指針,L1爲新節點。
head->next = L1;
tail = L1;
L1->next = NULL;
L1->prev = head;
head->prev = NULL;
4. 雙鏈表的實現
這裏只是簡單的實現一下,具體的一些複雜一點的操作在練習題中展示。
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node* prev;
};
class doubleList {
private:
Node *head, *tail;
public:
doubleList() //默認構造函數
{
head = NULL;
tail = NULL;
}
doubleList(int i) //重載構造函數
{
Node *p = new Node;
p->data = i;
head = p;
tail = p;
p->next = NULL;
p->prev =NULL;
}
~doubleList() //析構函數,釋放內存
{
while (head != NULL)
{
Node *q = head;
q = head->next;
delete head;
head = q;
}
}
void add_Node(int i) //向鏈表中插入節點
{
if (head == NULL)
{
Node *p = new Node;
p->data = i;
head = p;
tail = p;
p->next = NULL;
p->prev = NULL;
}
else
{
Node *p = new Node;
p->data = i;
tail->next = p;
p->next = NULL;
p->prev = tail;
tail = p;
}
}
void get_Node() //打印整個節點的內容
{
Node *q;
q = head;
while (q != NULL)
{
cout << q->data << " ";
q = q->next;
}
}
};
int main()
{
doubleList a(5);
a.add_Node(10);
a.add_Node(20);
a.get_Node();
return 0;
}
程序運行結果
四. 鏈表的練習
學習瞭如何實現鏈表後,下面就來通過一些例題鞏固所學的知識。
基礎練習
- 編寫一個成員函數,檢查兩個單鏈表的內容是否相同。
bool check_same(Node *L1, Node *L2)
{
Node *q1, *q2;
q1 = L1;
q2 = L2;
while (q1 != NULL && q2 != NULL)
{
if (q1->data != q2->data)
return false;
q1 = q1->next;
q2 = q2->next;
}
if ((q1 == NULL && q2 != NULL) || (q1 != NULL && q2 == NULL))
return false;
return true;
}
- 編程一個成員函數,只掃描一次就能將單鏈表反轉。
void reversal()
{
Node *q, *p, *r;
q = head->next;
p = head;
while (q->next != NULL)
{
r = q->next;
if (p == head)
p->next = NULL;
q->next = p;
p = q;
q = r;
}
q->next = p;
head = q;
}
- 向雙鏈表的正中間插入一個節點。
void insert_middle(Node head,Node p,int mid) //head爲鏈表的頭結點 p爲要插入的節點 //mid爲鏈表的中間位置
{
Node *q,*s;
q = head;
for (int i = 1; i < mid; q = q->next);
s = q->next;
q->next = p;
p->prev = q;
p->next = s;
s->prev = p;
}
4.將一個單鏈錶鏈接到另一個單鏈表的尾部
5. 從有序鏈表L中刪除鏈表L本身所給位置的節點。例如L=(1 3 5 7 8),那麼刪除之後,L=(3 7)
剩下兩道有興趣的自己試試。
綜合練習
1.一級Farey分數定義爲(0/1,1/1),此序列的二級形式是(0/1,1/2,1/1),三級形式是(0/1,1/3,1/2,2/3,1/1),四級形式是(0/1,1/4,1/3,1/2,2/3,3/4,1/1)。對應每一級n,只要c+d<=n,就要在兩相鄰分數a/c和b/d之間插入一個新的分數(a+b)/(c+d)。編寫程序,根據用戶輸入的n創建一個n級的Farey分數鏈表,並顯示其內容。
#include<iostream>
using namespace std;
#if 0
class Node {
public:
int a; //分子
int b; //分母
Node* next;
};
class List {
private:
Node* head, *tail;
int n;
public:
List() {
n = 1;
};
List(int i)
{
n = i;
}
~List()
{
Node *p;
p = head;
while (p)
{
head = p->next;
delete p;
p = head;
}
};
void init_1_farey()
{
Node *p = new Node;
p->a = 0;
p->b = 1;
head = p;
p->next = NULL;
tail = p;
Node*q = new Node;
q->a = 1;
q->b = 1;
tail->next = q;
q->next = NULL;
tail = q;
}
void get_farey()
{
Node *q = new Node;
Node *p = new Node;
Node *mid = new Node;
q = tail;
p = head;
mid = insert_node(head,tail);
q = mid;
while (head->next != p)
{
p = head->next;
insert_node(head, p);
}
while (true)
{
insert_node(q, tail);
q = q->next;
if (q == tail)
break;
}
}
Node* insert_node(Node* pre, Node *nex)
{
int d = pre->b + nex->b;
if (d <= n)
{
int a = pre->a+ nex->a;
Node *p = new Node;
p->a = a;
p->b = d;
pre->next = p;
p->next = nex;
return p;
}
}
void put_farey()
{
Node *p = new Node;
p = head;
while (p!= NULL)
{
cout << p->a <<'\\'<< p->b << " ";
p = p->next;
}
}
};
int main()
{
List a(5);
a.init_1_farey();
a.get_farey();
a.put_farey();
return 0;
}
#endif
五. 總結
- 鏈表的實現雖然複雜,但是解決了數組存在的內存問題,提升了程序的性能。
- 鏈表的存在形式多種多樣(單鏈表,雙鏈表,跳躍鏈表等等),可以滿足各種各樣的需求。
- c++的標準庫函數裏面也實現了鏈表,在list中,其效果還不賴。
Thank for your reading! ! !
ps:以上知識學習於《C++數據結構與算法》Adam Drozdek 著, 徐丹,吳偉敏 譯。