为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。这两部分信息组成数据元素的存储映像,称为结点。
n个结点链组成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
对于线性表来说,有头有尾,链表中的第一个结点的存储位置叫做头指针,链表的存取必须是从头指针开始进行。
有时候我了增加对链表操作的方便性,我们会在链表的第一个结点(首元结点)前增加一个结点,称为头结点。头结点的数据域可以不存任何数据,指针域存储第一个结点的指针。
链式存储结构中我们默认都带上头结点。
图片示意如下:
头指针和头结点的区别:
头指针:
头结点:
结点定义如下:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
/* ElemType类型根据实际情况而定,这里假设为int */
typedef int ElemType;
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;
//定义结点
typedef struct Node{
ElemType data; // 存储在数据域中的数据
struct Node *next; // 直接后继结点的地址指针
}Node;
typedef struct Node * LinkList;
思路:
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
假设要在单链表的两个数据元素A和B之间插⼊一个数据元素e,已知p为其单链表存储结构中指向结点A指针。如下图所示
思路:
代码实现如下:
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i-1个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i-1个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
要删除单链表中指定位置的元素,同插入元素一样,首先应该找到该位置的前驱结点,如下图所示在单链表中删除元素B时,应该首先找到其前驱结点A,为了在单链表中实现元素A,B,C之间的逻辑关系的变化,仅需修改结点A中的指针域即可.
思路:
代码实现如下:
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = *L;
j = 1;
//查找第i-1个结点,p指向该结点
while (p->next && j<i) {
p = p->next;
++j;
}
//当i>n 或者 i<1 时,删除位置不合理
if (!(p->next) || j>i) return ERROR;
//q指向要删除的结点
q = p->next;
//将q的后继赋值给p的后继
p->next = q->next;
//将q结点中的数据给e
*e = q->data;
//让系统回收此结点,释放内存;
free(q);
return OK;
}
在单链表中,我们不能像顺序存储结构那样直接通过下标直接获取数据,我们没办法一开始就知道,必须得从头开始找,进行遍历。
思路:
代码实现如下:
/*
初始条件: 顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(LinkList L,int i,ElemType *e){
int j = 1;
//声明结点p;
LinkList p;
//将结点p 指向链表L的首元结点;
p = L->next;
//p不为空,且计算j不等于i,则循环继续
while (p && j<i) {
//p指向下一个结点
p = p->next;
++j;
}
//如果p为空或者j>i,则返回error
if(!p || j > i) return ERROR;
//e = p所指的结点的data
*e = p->data;
return OK;
}
思路:
代码实现如下:
/* 随机产生n个元素值,建立带表头结点的单链线性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循环前插入随机数据
for(int i = 0; i < n;i++)
{
//生成新结点
p = (LinkList)malloc(sizeof(Node));
//i赋值给新结点的data
p->data = i;
p->next = (*L)->next;
//将结点P插入到头结点之后;
(*L)->next = p;
}
}
思路:
代码实现如下:
/* 随机产生n个元素值,建立带表头结点的单链线性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的结点
r = *L;
for (int i=0; i<n; i++) {
//生成新结点
p = (Node *)malloc(sizeof(Node));
p->data = i;
//将表尾终端结点的指针指向新结点
r->next = p;
//将当前的新结点定义为表尾终端结点
r = p;
}
//将尾指针的next = null
r->next = NULL;
}
思路:
代码实现如下:
/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一个结点 */
while(p) /* 没到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 头结点指针域为空 */
return OK;
}
存储分配方式:
时间性能:
空间性能:
通过对比,我们可得知:
若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。
当线性表中的元素个数变化较大或者根本不知道有多大的时候,最好采用单链表结构,这样可以不需要考虑存储空间大小的问题。而如果事先知道线性表的大致长度,就可以用这个顺序存储结构,用起来比较高效。
将单链表中的终端结点的指针从空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
为了使空链表与非空链表处理一致,我们通常设一个头结点。
循环列表中带有头结点的空链表如下图:
对于非空的循环链表如下图:
思路:
代码实现:
/* 创建一个带有头结点的循环链表 */
Status CreateList(LinkList *L) {
int item;
LinkList temp = NULL;
LinkList r = NULL; // 尾结点指针
printf("请输入新的结点, 当输入0时结束!\n");
while (1) {
scanf("%d",&item);
if (item == 0) {
break;
}
// 当链表为空时,创建链表,带上头结点。
if (*L == NULL) {
*L = (LinkList)malloc(sizeof(Node));
if (!*L) return ERROR;
(*L)->next = *L; // 使其next指针指向自己
r = *L;
}
temp = (LinkList)malloc(sizeof(Node));
if(!temp) return ERROR;
temp->data = item;
temp->next = *L;
r->next = temp;
r = temp;
}
return OK;
}
思路:
代码实现如下:
/* 循环链表插入数据 */
Status ListInsert2(LinkList *L, int place, int num) {
if (place < 1) {
return ERROR;
}
LinkList target;
LinkList temp;
int k;
temp = (LinkList)malloc(sizeof(Node));
if (!temp) return ERROR;
temp->data = num;
// 查找插入位置的前一个结点
for (k = 1, target = *L; k < place && target->next != *L; k++, target = target->next);
temp->next = target->next;
target->next = temp;
return OK;
}
思路:
代码实现如下:
/* 循环链表删除数据,链表表L已存在,1≤place≤ListLength(L) */
Status LinkListDelete(LinkList *L,int place) {
if (place < 1) {
return ERROR;
}
LinkList temp, target;
int k;
for (k = 1, target = *L; k < place && target->next != *L; k++, target = target->next);
if (place > k) {
return ERROR;
}
temp = target->next;
target->next = temp->next;
free(temp);
return OK;
}
思路:
代码实现如下:
/* 循环链表查询值的位置 */
int findValue(LinkList L, int value) {
int i = 1;
LinkList p;
p = L->next;
while (p->data != value && p->next != L) {
i++;
p = p->next;
}
// 如果已经遍历到最后一个元素,且还没有找到,那么直接返回-1.
if (p->next == L && p->data != value) {
return -1;
}
return i;
}