在讨论单链表(带头节点,不带头节点类似,这里不再赘述)之前我们已经学习过线性表的顺序存储结构,我们知道它是有缺点的,最大的缺点就是插入和删除元素时需要移动大量元素、并且需要预先分配存储空间,但是优点是存取方便。
线性表的链式存储结构特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。其存储结构如下:
/*线性表的链式存储结构*/
typedef int Elemtype;
typedef struct Node
{
Elemtype data;
struct Node* next;
};
typedef struct Node* LinkList;
单链表创建
单链表的创建过程就是一个动态生成链表的过程,按照插入方式可分为头插法和尾插法,头插法代码实现如下:
LinkList CreateListHead(int n)
{
LinkList p;
srand(time(0)); //初始化随机数种子
LinkList L = (LinkList)malloc(sizeof(Node)); //建立带头节点的单链表
L->next = NULL;
for (int i= 0; i < n; ++i)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
p->next = L->next;
L->next = p; //插入到表头
printf("插入数据%d\n", p->data);
}
return L;
}
代码中始终让新节点放在第一个节点的位置,即头插法。我们也可以把新的节点放在最后面,即尾插法,代码实现如下:
/*创建含有n个元素的带有头结点的单链表(尾插法)*/
LinkList CreateListTail(int n)
{
LinkList p, r;
srand(time(0)); //初始化随机数种子
LinkList L = (LinkList)malloc(sizeof(Node)); //建立带头节点的单链表
L->next = NULL;
r = L;
for (int i = 0; i < n; ++i)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p;
r = p;
printf("插入数据%d\n", p->data);
}
r->next = NULL;
return L;
}
代码中用变量r来记录每次上次插入后链表的尾端位置,最后插入结束后将r的指针域置空。
单链表删除
完成了单链表的创建,如果我们不想使用这个链表了,想销毁它,也就是在内存中释放它,实现代码如下:
/*置空链表L*/
Status ClearList(LinkList L)
{
LinkList p, q;
p = L->next;
while (p)
{
q = p->next;
free(p);
p = q;
}
L->next = NULL;
return OK;
}
单链表元素获取
实现获取单链表第i个元素的实现代码如下:
/*用e返回链表L中第i元素的值*/
Status GetElem(LinkList L, int i, Elemtype*e)
{
LinkList p;
p = L->next;
int j = 1;
while (p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
{
return ERROR;
}
*e = p->data;
return OK;
}
单链表的插入
单链表的插入只需要改变插入位置前后节点的关系即可。实现代码如下:
/*在单链表L中第i个位置插入新的数据元素e*/
Status ListInsert(LinkList L, int i, Elemtype e)
{
LinkList p, s;
p = L;
int j = 1;
while (p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
{
return ERROR;
}
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
单链表的删除
单链表删除节点,其实就是修改待删除节点的前后两个节点的next指针域。实现代码如下:
Status ListDelete(LinkList L, int i, Elemtype *e)
{
LinkList p, q;
p = L;
int j = 1;
while (p->next && j < i)
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
{
return ERROR;
}
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
分析上述插入和删除的代码,可以发现它们其实就是由两部分组成,第一部分是遍历查找第i个节点,第二部分就是插入或者删除。从整个算法来说,我们可以推算出它们的时间复杂度都是O(n)。如果我们不知道第i个元素的位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储结构相比是没有太大的优势的,但如果,我们希望从第i个位置插入10个元素,对于顺序存储结构来说吗,每一次插入都要移动n-i个元素,每次都是O(n),而单链表只需要在第一次时,找到第i个位置的节点,此时为O(n),接下来只是简单的赋值移动指针而已,时间复杂度都是O(1),对于插入删除数据越频繁的操作,单链表的效率优势就越明显。