前言
T_T此专栏用于记录数据结构及算法的(痛苦)学习历程,便于日后复习(这种事情不要啊)。所用教材为《数据结构 C语言版 第2版》严蔚敏。
有关线性表的顺序存储见线性表概念及顺序表的实现;
有关单循环链表见线性表的链式存储(单循环链表);
有关双向链表见线性表的链式存储(双向链表)。
有关双向循环链表见线性表的链式存储(双向循环链表)
一、概念及特点
链式存储结构:数据在空间中的存储位置并非连续,而是呈现满天飞的形式。数据间通过一条无形的链连接起来,即指针。从而,使用链式存储结构的数据域,同时应具有对应的指针域,数据域存放数据,指针域指示其后继的信息。
线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。n个结点(ai(1<=i<=n)的存储映像)链结成一个链表,即为线性表(a1,a2,a3…an)的链式存储结构。
二、链表术语及分类
头指针:指向链表中的第一个结点的指针。
首元节点:指链表中存储第一个数据元素a1的结点。
头结点:在链表首元结点之前附设的一个结点,不属于链表数据元素,不计入链表长度,用于存储一些附加信息,如链表的长度。
根据链表结点所含指针个数、指针指向和指针连接方式,可将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等。其中单链表、循环链表和双向链表用于实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构。
单链表:每个结点指针域中只包含一个指针(指示直接后继),又称线性链表。
单向循环链表:每个结点指针域中只包含一个指针(指示直接后继),最后一个结点指针域指向头结点。
双向链表:每个结点指针域中包含两个指针(指示直接后继和直接前驱),最后一个结点指针域指向头结点。
三、单链表
1.特点
整个链表的存取必须从头指针开始进行(顺序存取),头指针指示链表中第一个结点(即第一个数据元素的存储映像,也称首元结点)的存储位置。同时,由千最后一个数据元素没有直接后继,则单链表中最后一个结点的指针为空(NULL)。单链表可由头指针唯一确定,因此单链表可以用头指针名字来命名,如下示例可称为单链表L。
2.C语言实现
//----- 单链表的存储结构-----
typedef struct LNode {
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域
}LNode,*LinkList;
//LinkList 为指向结构体 LNode 的指针类型
注:
(1)这里定义的是单链表中每个结点的存储结构,它包括两部分:存储结点的数据域 data, 其类型用通用类型标识符 ElemType 表示(例如存储整型变量用int即可);存储后继结点位置的指针域next, 其类型为指向结点的指针类型LNode。
(2) 为了提高程序的可读性,在此对同一结构体指针类型起了两个名称,LinkList 与LNode*,两者本质上是等价的。通常习惯上用LinkList 定义单链表,强调定义的是某个单链表的头指针;用 LNode* 定义指向单链表中任意结点的指针变量。例如,若定义 LinkList L,则L为单链表的头指针,若定义 LNode* p, 则p为指向单链表中某个结点的指针,用* p代表该结点。当然也可以使用定义 LinkList p, 这种定义形式完全等价于LNode* p。
(3) 注意区分指针变量和结点变量两个 不同的概念,若定义=LinkList p或LNode* p,则p为指向某结点的指针变量,表示该结点的地址;而* p为对应的结点变量,表示该结点的名称。
3.头结点作用
一般情况下,为了处理方便,在单链表的第一个结点之前附设一个头结点。
链表增加头结点的作用如下。
(1)便于首元结点的处理
增加了头结点后,首元结点的地址保存在头结点(即其“前驱”结点)的指针域中,则对链表的第一个数据元素的操作与其他数据元素相同,无需进行特殊处理。
(2)便于空表和非空表的统一处理
当链表不设头结点时,假设L为单链表的头指针,它应该指向首元结点,则当单链表为长度n 为 0 的空表时,L 指针为空(判定空表的条件可记为:L== NULL)。增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针,如下图(a)所示的非空单链表,头指针指向头结点。若为空表,则头结点的指针域为空(判定空表的条件可记为:L->next== NULL),如下图(b)所示的空单链表。
4.基本操作的具体实现
有关一般线性表的抽象数据类型的定义见线性表概念及顺序表的实现
下面给出单链表创建和增删改查等操作的具体实现:
#include <iostream>
using namespace std;
#define OK 1
#define fail 0
#define overflow -1
typedef int Status;
typedef int Elemtype;
typedef struct Lnode{
Elemtype data;
Lnode * next;
}Lnode,*Llist;
Llist L;
Elemtype e;
//初始化单链表,创建头指针和头结点
Status InitList(Llist &L)
{
L = new Lnode; //头指针
if (!L)return fail; //如果创建失败
L->data = 123456; //头结点
L->next = NULL;
return OK;
}
//创建单链表,头插法,创建i个结点
//先创建的后访问
Status CreatList_Head(Llist L,int i)
{
if (i < 1)return fail; //至少创建1个结点
while (i--)
{
Lnode* p = new Lnode;
cin >> p->data;
p->next = L->next;
L->next = p;
}
return OK;
}
//创建单链表,尾插法,创建i个结点
//先创建的先访问
Status CreatList_End(Llist L, int i)
{
Llist p=L;
if (i < 1)return fail; //至少创建1个结点
while (i--)
{
Lnode* q = new Lnode;
cin >> q->data;
q->next = NULL;
p->next = q;
p = q;
}
return OK;
}
//得到第i个结点的值,注意首元结点开始算第一个结点
//e为得到的结点数据值
Status GetElem(Llist L, int i,Elemtype *e)
{
Llist p = L;
if (i < 1 || L->next == NULL)return overflow; //如果i不合法或链表为空,则失败
for (i; i > 0; i--)
{
if (p->next != NULL)
p = p->next;
else
return overflow;
}
*e = p->data;
return OK;
}
//查找某个值在链表中的位置,注意首元结点开始算第一个结点
//e为要查找的数据值,ElemPosition为在链表中的位置
Lnode * LocatElem(Llist L, Elemtype e,int &ElemPosition)
{
ElemPosition = 1;
Llist p = L->next;
while (p && p->data != e)
{
p = p->next;
ElemPosition++;
}
return p;
}
//在第i个位置前插入一个结点,注意首元结点开始算第一个结点
//e插入的结点的数据值
Status InsertElem(Llist L, int i,Elemtype e)
{
Llist p = L;
if (i < 1)return fail; //如果i不合法则失败
while (p && i>1)
{
p = p->next;
i--;
}
if (!p || i!=1)return fail; //如果链表不存在第i个位置,则失败
Lnode*q = new Lnode;
q->data = e;
q->next = p->next;
p->next = q;
return OK;
}
//删除在第i个位置的结点,注意首元结点开始算第一个结点
Status DeleteElem(Llist L, int i)
{
Llist p = L;
if (i < 1)return fail; //如果i不合法则失败
while (p&&i>1)
{
p = p->next;
i--;
}
if (!p || i != 1)return fail; //如果链表不存在第i个位置,则失败
Llist q = p->next;
p->next = q->next;
delete q;
q = NULL;
return OK;
}
//销毁链表
Status DestoryList(Llist L)
{
Llist p = L;
while (p)
{
Llist q = p;
p = p->next;
delete q;
q = NULL;
}
return OK;
}
int main()
{
//进行测试
InitList(L);
cout << L << endl;
cout << L->data << endl;
cout << L->next << endl;
CreatList_End(L, 5);
cout << InsertElem(L, 1, 99) << endl;
cout << GetElem(L, 1, &e) << endl << e << endl;
DeleteElem(L, 2);
for (int i=0;i<7;i++)
cout<< GetElem(L, i, &e)<<e<<" ";
DestoryList(L);
cout << endl << L->data << endl;
return 0;
}
测试现象:
总结
路漫漫其修远兮,吾将上下而开摆。
有任何疑问和补充,欢迎交流。(但我显然不会)