最近在学习严蔚敏教授的《数据结构》,有一些感想,在这里写下来,留做笔记,希望能对以后学习有所帮助。
我们知道,线性表就是n个数据元素的有限序列,它有两种实现方式,其一为顺序表,即数组+伪指针的方式实现,另一为链表,采用的是指针的方式实现。由于顺序表的知识偏向基础且其应用灵活性不高,故在此不再介绍,本文主要说明几种常用的链表是如何实现的以及其基本操作。
1.单链表:
一个完整的单链表如下图:
由此可见,链表每一个节点都是由数据域和指针域构成的,每一个指针域指向下一个节点,每个节点的数据域存储相应的数据,(头节点除外,一般头节点不存储任何数据,但也可以用来存储表长等信息),最后一个节点的指针域指向NULL,即空,表示链表结束。由于单链表的这种特性,想访问单链表中任何一个元素都必须通过头结点层层往下遍历,因此头节点是链表中最重要的节点,没有头节点就无法访问整个链表。这也是链表和顺序表最大的不同,顺序表是数组实现,访问节点以及返回表长都很容易实现,但是它没有动态分配空间的功能,只能一开始初始化一个较大的空间以便以后利用,一旦填满,则顺序表不能再插入元素,而链表则可以动态改变长度,加之指针的方式使得其遍历速度极为高效,其灵活性和可靠性使得链表成为线性表的主要实现方式,也是栈和队列等重要数据结构类型的基础。
单链表的数据元素插入原理:
若要在第i个位置上插入数据,则需要遍历单链表到第i-1个位置,然后利用malloc()函数向系统请求空间开辟新节点T
1.先使得T的指针域 指向 第i-1个数据的指针域指向的位置(即第i个元素);
2.再让第i-1个数据的指针域 指向 T;
3.以上两步千万不能颠倒顺序,否则将使得整个链表丢失i位置后的所有数据元素;
单链表的数据元素删除原理:
若要删除第i个位置上的数据,也需遍历到第i-1个位置,然后利用malloc()函数向系统请求开辟新节点P
1.P的指针域 指向 第i-1的指针域指向的位置(即第i个元素);
2.第i-1个元素指针域 指向 第i个元素的指针域指向的位置(即第i+1个元素);
3.释放P节点占用的空间(即删除了第i个节点);
还有一些其他的操作,比如获得某个位置上的节点,返回单链表长度,清空/销毁链表,遍历整个链表,都是简单的方法,理解了指针就不难写,原理在此不提,关于单链表的代码,就在这里放出一段简单的实例:
#include
#include
#include
using namespace std;
typedef struct Node
{
int data;
struct Node *next;
}*LinkedList,LNode;
void CreatLinkedList(LinkedList &L,int n) //尾插法创建单链表;
{
L = (LinkedList)malloc(sizeof(LNode)); //初始化;
L->next = NULL;
L->data = 0;
LinkedList Tail = L; //尾指针;
cout<<"Enter "<>Temp->data;
Tail->next = Temp;
Tail = Temp;
Temp = NULL;
L->data++; //计数;
}
Tail->next = NULL;
}
bool GetElem(int &e,int i,LinkedList L) //获取结点;
{
while(L != NULL && i > 0)
{
i--;
L = L->next;
}
if(i == 0 && L != NULL) //i=1时也有可能同时满足退出while的条件;
{
e = L->data;
return true;
}
else return false;
}
bool InsertElem(int e,int i,LinkedList L) //插入结点;
{
if(i > L->data+1 || i < 1) return false;
else
{
L->data++;
while(i > 1)
{
i--;
L = L -> next;
}
LinkedList Temp = (LinkedList)malloc(sizeof(LNode));
Temp->data = e;
Temp->next = L->next;
L->next = Temp;
Temp = NULL;
return true;
}
}
bool DeleteElem(int i,LinkedList L) //删除结点;
{
if(i > L->data || i < 1) return false;
else
{
L->data--;
while(i > 1)
{
i--;
L = L->next;
}
LinkedList Temp = (LinkedList)malloc(sizeof(LNode));
Temp = L->next;
L->next = Temp->next;
free(Temp);
Temp = NULL;
return true;
}
}
bool DestoryLinkedList(LinkedList &L) //销毁单链表;
{
if(L->next != NULL)
DestoryLinkedList(L->next);
free(L);
L = NULL;
return true;
}
bool ClearLinkedList(LinkedList &L) //清空单链表;
{
DestoryLinkedList(L->next);
L->next = NULL;
L->data = 0;
return true;
}
void GetLinkedList(LinkedList L) //遍历单链表;
{
LinkedList Head = L->next;
while(Head != NULL)
{
cout<data<next;
}
}
int main()
{
int n,i,Elem;
bool Flag;
LinkedList L;
cout<<"How many Elem(s) do you want to creat?"<>n;
CreatLinkedList(L,n);
cout<<"Here is what they look like:"<>i;
Flag = GetElem(Elem,i,L);
if(Flag == true)
cout<>Elem;
cout<<"Position :";
cin>>i;
Flag = InsertElem(Elem,i,L);
if(Flag == true)
{
cout<<"Succeeded!"<>i;
Flag = DeleteElem(i,L);
if(Flag == true)
{
cout<<"Succeeded!"<
2.循环链表
和单链表极其相似的一种结构就是循环链表了,它的节点结构和单链表一模一样,唯一的区别就是它最后一个数据元素不指向NULL,而是指向头指针,这样的链表构成了一个环,因此成为循环链表。
一个完整的循环链表如下图:
循环链表的操作和单链表基本一致,差别仅在于算法中的循环条件不是L或L->为空,而是它们是否等于头指针,因为当循环到头指针,说明链表已经完整遍历一次,下面给出代码:
#include
#include
#include
using namespace std;
typedef struct LNode
{
int data;
struct LNode *next;
}*CircleLinkedList,LNode;
void CreatLinkedList(CircleLinkedList &L,int n) //尾插法创建循环链表;
{
L = (CircleLinkedList)malloc(sizeof(LNode)); //初始化;
L->next = NULL;
L->data = 0;
CircleLinkedList Tail = L; //尾指针;
cout<<"Enter "<>Temp->data;
Tail->next = Temp;
Tail = Temp;
L->data++;
}
Tail->next = L;
}
bool GetElem(int &e,int i,CircleLinkedList L) //获取结点;
{
CircleLinkedList Head = L;
while(L->next != Head && i > 0)
{
i--;
L = L->next;
}
if(i == 0 && L != Head) //i=1时也有可能同时满足退出while的条件;
{
e = L->data;
return true;
}
else return false;
}
bool InsertElem(int e,int i,CircleLinkedList L) //插入结点;
{
if(i > L->data+1 || i < 1) return false;
else
{
L->data++;
while(i > 1)
{
i--;
L = L -> next;
}
CircleLinkedList Temp = (CircleLinkedList)malloc(sizeof(LNode));
Temp->data = e;
Temp->next = L->next;
L->next = Temp;
return true;
}
}
bool DeleteElem(int i,CircleLinkedList L) //删除结点;
{
if(i > L->data || i < 1) return false;
else
{
L->data--;
while(i > 1)
{
i--;
L = L->next;
}
CircleLinkedList Temp = (CircleLinkedList)malloc(sizeof(LNode));
Temp = L->next;
L->next = Temp->next;
free(Temp);
Temp = NULL;
return true;
}
}
bool DestoryCircleLinkedList(CircleLinkedList &L,CircleLinkedList Head) //销毁循环链表;
{
if(L->next != Head)
DestoryCircleLinkedList(L->next,Head);
free(L);
L = NULL;
return true;
}
bool ClearCircleLinkedList(CircleLinkedList &L,CircleLinkedList Head) //清空循环链表;
{
DestoryCircleLinkedList(L->next,Head);
L->next = Head;
L->data = 0;
return true;
}
void GetCircleLinkedList(CircleLinkedList L) //遍历循环链表;
{
CircleLinkedList Head = L;
L = L->next;
while(L != Head)
{
cout<data<next;
}
}
int main()
{
int n,i,Elem;
bool Flag;
CircleLinkedList L;
cout<<"How many Elem(s) do you want to creat?"<>n;
CreatLinkedList(L,n);
cout<<"Here is what they look like:"<>i;
Flag = GetElem(Elem,i,L);
if(Flag == true)
cout<>Elem;
cout<<"Position :";
cin>>i;
Flag = InsertElem(Elem,i,L);
if(Flag == true)
{
cout<<"Succeeded!"<>i;
Flag = DeleteElem(i,L);
if(Flag == true)
{
cout<<"Succeeded!"<
3.双向链表
由于单链表仅具有单向性的特点,我们引入了双向链表来克服这个缺点,顾名思义,双向链表就是能通过当前节点访问下一个/上一个节点的链表结构。
一个完整的双向链表:
注:上图中方便起见,将最后一个节点指向NULL,实际上可以指向一个尾指针,这里可以灵活修改
双向链表的数据元素插入原理:
若想在第i个位置上插入数据,则先遍历到第i-1个位置,使用malloc()函数向系统请求一个新节点T
1.让T->pre 指向 第i-1个节点;
2.让T->next 指向 第i-1个结点的下一个节点(即第i个节点);
3.让i->next 指向 T;
4.让i->pre指向T;(前提是第i个节点非空)
双向链表的数据元素删除原理:
若想在第i个位置上插入数据,则先遍历到第i-1个位置,使用malloc()函数向系统请求一个新节点P,且让P指向第i个数据
1.让P->next->pre 指向 P->pre;(前提是第i+1个节点非空)
2.让i->next 指向 p->next;
3.释放P节点所占用的空间;(即删除了第i个节点)
放出一段双向链表代码实例:
#include
#include
#include
using namespace std;
typedef struct LNode
{
int data;
struct LNode *pre;
struct LNode *next;
}*DoubleLinkedList,LNode;
void CreatDoubleLinkedList(DoubleLinkedList &L,int n) //尾插法创建双链表;
{
L = (DoubleLinkedList)malloc(sizeof(LNode));
L->pre = NULL;
L->next = NULL;
L->data = 0;
DoubleLinkedList Tail = L;
cout<<"Enter "<>Temp->data;
Tail->next = Temp;
Temp->pre = Tail;
Tail = Temp;
L->data++;
}
Tail->next = NULL;
}
bool GetElem(int &e,int i,DoubleLinkedList L) //获取结点;
{
while(L != NULL && i > 0)
{
i--;
L = L->next;
}
if(i == 0 && L != NULL)
{
e = L->data;
return true;
}
else return false;
}
bool InsertElem(int e,int i,DoubleLinkedList L) //插入结点;
{
if(i > L->data+1 || i < 1)
return false;
else
{
L->data++;
while(i > 1)
{
L = L->next;
i--;
}
DoubleLinkedList Temp = (DoubleLinkedList)malloc(sizeof(LNode));
Temp->data = e;
if(L->next != NULL)
{
Temp->next = L->next;
Temp->pre = L;
L->next->pre = Temp;
L->next = Temp;
}
else
{
Temp->pre = L;
L->next = Temp;
Temp->next = NULL;
}
}
}
bool DeleteElem(int i,DoubleLinkedList L) //删除结点;
{
if(i > L->data || i < 1)
return false;
else
{
L->data--;
while(i > 1)
{
L = L->next;
i--;
}
DoubleLinkedList Temp = (DoubleLinkedList)malloc(sizeof(LNode));
Temp = L->next;
if(L->next->next != NULL)
{
L->next->next->pre = L;
L->next = L->next->next;
}
else
L->next = NULL;
free(Temp);
Temp = NULL;
return true;
}
}
bool DestoryDoubleLinkedList(DoubleLinkedList &L) //销毁双链表;
{
if(L->next != NULL)
DestoryDoubleLinkedList(L->next);
free(L);
L = NULL;
return true;
}
bool ClearDoubleLinkedList(DoubleLinkedList &L) //清空双链表;
{
DestoryDoubleLinkedList(L->next);
L->next = NULL;
L->data = 0;
return true;
}
void GetDoubleLinkedList(DoubleLinkedList L) //遍历单链表;
{
DoubleLinkedList Head = L->next;
while(Head != NULL)
{
cout<data<next;
}
}
int main()
{
int n,i,Elem;
bool Flag;
DoubleLinkedList L;
cout<<"How many Elem(s) do you want to creat?"<>n;
CreatDoubleLinkedList(L,n);
cout<<"Here is what they look like:"<>i;
Flag = GetElem(Elem,i,L);
if(Flag == true)
cout<>Elem;
cout<<"Position :";
cin>>i;
Flag = InsertElem(Elem,i,L);
if(Flag == true)
{
cout<<"Succeeded!"<>i;
Flag = DeleteElem(i,L);
if(Flag == true)
{
cout<<"Succeeded!"<
以上就是要记录的内容了,下面说几点注意的地方:
1.由于双链表具有双向性,它自然也就有单向性,因此它的清空和删除和单链表完全一致;
2.循环链表清空删除和单链表不一致,主要体现在二者“空”状态不一致,可见上图;
3.这里声明的变量:LinkedList a; a是该结构体类型指针型的变量,见下面定义:
typedef struct LNode
{
int data;
struct LNode *next;
}*LinkedList;
4.关于free()函数,它只能释放由calloc(),malloc(),realloc()申请的动态空间,不可以释放用户自定义的空间,若把上面代码的清空单链表函数改为如下,则相当于没有清空,对单链表没任何影响,我看许多人都犯这个错误,特地写一下:
bool free_list(LinkedList head)
{
LinkedList pointer;
while(head != NULL)
{
pointer = head;
head = head->next;
free(pointer);
}
return true;
}
5.实际上双链表也是有循环双链表的,我在这里不写,原理是一样的,感兴趣可以自己另作研究。