爲了表示每個數據元素ai與其直接後繼元素ai+1之間的邏輯關係,對數據ai,除了存儲其自身的信息之外,還需存儲一個指示其直接後繼的信息(即直接後繼的存儲位置)。這兩部分信息組成數據元素ai的存儲映像,稱爲結點(Node)。N個結點鏈結成一個鏈表,即爲線性表(a1,a2,…,an)的鏈式存儲結構,因爲此鏈表的每個節點中只包含一個指針域,所以叫做單鏈表。我們把鏈表中的第一個結點的存儲位置叫做頭指針,,爲了更方便地對鏈表進行操作,如刪除第一個結點的特殊情況(第一個結點沒有前驅,而要摘除一個結點需要首先找到它的前驅才能做摘除操作),經常在單鏈表的第一個結點前附設一個結點,稱爲頭節點,這樣頭指針就指向了頭節點。
單鏈表與數組的優缺點基本上是相反的,即單鏈表插入刪除效率高,而查找需要遍歷,效率較低。
示例程序:(改編自《大話數據結構》,增加了鏈表反轉等)
#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef Node *NodePtr;
bool InitList(NodePtr *Npp)
{
*Npp = (NodePtr)malloc(sizeof(Node));/* 產生頭結點,並使*Npp指向此頭結點 */
if (!(*Npp))
return false; //分配失敗
(*Npp)->next = NULL;/* 指針域爲空 */
return true;
}
bool ListEmpty(NodePtr Np)
{
if (Np->next == NULL)
return true;
else
return false;
}
bool ClearList(NodePtr *Npp)
{
cout << "Clear List..." << endl;
NodePtr p = *Npp;
if (!(p->next))
return true;
while (p->next)/* 沒到表尾 */
{
NodePtr q = p->next;
p->next = q->next;
free(q);
}
return true;
}
int ListLength(NodePtr Np)
{
cout << "List's length : ";
NodePtr p = Np->next;/* p指向第一個結點 */
int i = 0;
while (p)
{
p = p->next;
++i;
}
return i;
}
/* 操作結果:用ptr返回Np中第pos個數據元素的值 */
bool GetElem(NodePtr Np, int pos, ElemType *ptr)
{
cout << "Get Item from pos " << pos << " : ";
NodePtr p = Np->next;
int i = 1;
/* p不爲空或者計數器i還沒有等於pos時,循環繼續 */
while (p && i < pos)
{
p = p->next;
++i;
}
if (!p)
return false;
*ptr = p->data;/* 取第pos個元素的數據 */
return true;
}
/* 返回Np中第1個與Elem滿足關係的數據元素的位序。 */
/* 若這樣的數據元素不存在,則返回值爲0 */
int LocateElem(NodePtr Np, ElemType Elem)
{
cout << "Item " << Elem << "'s pos : ";
NodePtr p = Np->next;
int i = 1;
while (p && p->data != Elem)
{
p = p->next;
++i;
}
if (!p)
return 0;
return i;
}
/* 操作結果:在Np中第pos個位置之前插入新的數據元素Elem,Np的長度加1 */
bool ListInsert(NodePtr *Npp, int pos, ElemType Elem)
{
cout << "Insert List pos " << pos << " Item " << Elem << endl;
NodePtr p = *Npp;
int i = 1;
while (p && i < pos)
{
p = p->next;
++i;
}
if (!p)
return false;
NodePtr In = (NodePtr)malloc(sizeof(Node));
In->data = Elem;
In->next = p->next;/* 將p的後繼結點賦值給In的後繼 */
p->next = In; /* 將In賦值給p的後繼 */
return true;
}
/* 刪除Npp的第pos個數據元素,並用ptr返回其值,Npp的長度減1 */
bool ListDelete(NodePtr *Npp, int pos, ElemType *ptr)
{
cout << "Delete List Item in pos " << pos << endl;
NodePtr p = *Npp;
int i = 1;
while (p && i < pos)
{
p = p->next;
++i;
}
if (!p)
return false;
NodePtr q = p->next;
*ptr = q->data;
p->next = q->next;/* 將q的後繼賦值給p的後繼 */
free(q);
return true;
}
bool ListTraverse(NodePtr Np)
{
cout << "List's Items : ";
NodePtr p = Np->next;
while (p)
{
cout << p->data << ' ';
p = p->next;
}
cout << endl;
return true;
}
/* 隨機產生n個元素的值,建立帶表頭結點的單鏈線性表Npp(頭插法) */
void CreateListHead(NodePtr *Npp, int num)
{
cout << "Create List from Head ..." << endl;
if (*Npp != NULL)
free(*Npp);
*Npp = (NodePtr)malloc(sizeof(Node));
(*Npp)->next = NULL;/* 先建立一個帶頭結點的單鏈表 */
srand(time(NULL));
for (int i = 0; i < num; i++)
{
NodePtr p = (NodePtr)malloc(sizeof(Node));
p->data = rand() % 100 + 1; //隨機數
p->next = (*Npp)->next;
(*Npp)->next = p;/* 插入到表頭 */
}
}
/* 隨機產生n個元素的值,建立帶表頭結點的單鏈線性表Npp(尾插法) */
void CreateListTail(NodePtr *Npp, int num)
{
cout << "Create List from Tail ..." << endl;
if (*Npp != NULL)
free(*Npp);
*Npp = (NodePtr)malloc(sizeof(Node));
(*Npp)->next = NULL;
srand(time(NULL));
NodePtr tail = *Npp; /* tail爲指向尾部的結點 */
for (int i = 0; i < num; i++)
{
NodePtr p = (NodePtr)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
tail->next = p; /* 將表尾終端結點的指針指向新結點 */
tail = p; /* 將當前的新結點定義爲表尾終端結點 */
}
tail->next = NULL;
}
/* 反轉單鏈表*/
NodePtr ReverseList(NodePtr head)
{
cout << "Reverse List ...." << endl;
if(NULL == head->next || NULL == head->next->next)
return head;
NodePtr p;
NodePtr q;
NodePtr r;
p = head->next;
q = p->next;
head->next->next = NULL;
while(q)
{
r = q->next; //
q->next = p;
p = q; //
q = r; //
}
head->next = p;
return head;
}
int main(void)
{
NodePtr Np; //頭指針
InitList(&Np);
for (int i = 1; i < 5; i++)
ListInsert(&Np, i, i);
if (!ListEmpty(Np))
cout << ListLength(Np) << endl;
ListTraverse(Np);
cout << LocateElem(Np, 3) << endl;
int get;
GetElem(Np, 2, &get);
cout << get << endl;
ClearList(&Np);
cout << ListLength(Np) << endl;
CreateListHead(&Np, 10);
ListTraverse(Np);
int result;
ListDelete(&Np, 5, &result);
ListTraverse(Np);
ClearList(&Np);
CreateListTail(&Np, 10);
ListTraverse(Np);
ListTraverse(ReverseList(Np));
ClearList(&Np);
return 0;
}
輸出爲:
注意:程序中使用的單鏈表反轉的方法是:使用p和q連個指針配合工作,使得兩個節點間的指向反向,同時用r記錄剩下的鏈表。
下圖是不含頭節點時的單鏈表反轉情況,含頭節點的單鏈表反轉有細微區別,需小心。