前言
前面我们讲的线性表的顺序存储结构,它最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。那我们能不能针对这个缺陷或者说遗憾提出解决的方法呢?要解决这个问题,我们就得考虑一下导致这个问题的原因!
为什么当插入和删除时,就要移动大量的元素?原因就在于相邻两元素的存储位置也具有邻居关系,它们在内存中的位置是紧挨着的,中间没有间隙,当然就无法快速插入和删除。
一、链式线性表
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。比起顺序存储结构每个数据元素只需要存储一个位置就可以了。
二、代码示例
chainlist.h
#ifndef __CHAIN_LIST_
#define __CHAIN_LIST_
typedef int ElemType;
typedef struct _node {
ElemType data;
struct _node *next;
} chainNode_s, *chainNode_p;
int initList(chainNode_s **head);
int lengthList(chainNode_p head);
int addListOfHead(chainNode_p head, ElemType value);
int addListOfTail(chainNode_p head, ElemType value);
int insertList(chainNode_p head, ElemType value, int i);
int getValueList(chainNode_p head, ElemType *value, int i);
int deleteList(chainNode_p head, int i);
int deleteListOfTail(chainNode_p head);
int freeList(chainNode_p head);
int printfList(chainNode_p head);
#endif
chainlist.c
#include <stdio.h>
#include <stdlib.h>
#include "chainlist.h"
/*初始化链式线性表*/
int initList(chainNode_s **head)
{
*head = (chainNode_s *)malloc(sizeof(chainNode_s));
if (NULL == *head) {
perror("malloc error");
return -1;
}
(*head)->data = 0;
(*head)->next = NULL;
return 0;
}
/*可以将头结点的数设置为线性表的长度,此没有实现*/
int lengthList(chainNode_p head)
{
return head->data;
}
/*在线性表头部添加节点*/
int addListOfHead(chainNode_p head, ElemType value)
{
chainNode_p node;
if (head == NULL)
return -1;
node = (chainNode_p)malloc(sizeof(*node));
if (NULL == node) {
perror("malloc error");
return -1;
}
node->data = value;
if (NULL == head->next) {
node->next = NULL;
head->next = node;
} else {
node->next = head->next;
head->next = node;
}
return 0;
}
/*在线性表尾部添加节点*/
int addListOfTail(chainNode_p head, ElemType value)
{
chainNode_p node;
chainNode_p p;
if (head == NULL)
return -1;
node = (chainNode_p)malloc(sizeof(*node));
if (NULL == node) {
perror("malloc error");
return -1;
}
node->data = value;
node->next = NULL;
if (NULL == head->next) {
node->next = NULL;
head->next = node;
}
p = head->next;
while(p->next && (p = p->next));
p->next = node;
return 0;
}
/*在线性表 i 处插入节点*/
int insertList(chainNode_p head, ElemType value, int i)
{
chainNode_p node;
chainNode_p p;
if (head == NULL || i <= 0)
return -1;
node = (chainNode_p)malloc(sizeof(*node));
if (NULL == node) {
perror("malloc error");
return -1;
}
node->data = value;
if (NULL == head->next) {
node->next = NULL;
head->next = node;
} else {
p = head;
/*得到插入节点处的上一个节点*/
while(p->next && ((--i) > 0))
p = p->next;
node->next = p->next;
p->next = node;
}
return 0;
}
/*取出节点 i 的数据*/
int getValueList(chainNode_p head, ElemType *value, int i)
{
chainNode_p p;
if (head == NULL || i <= 0)
return -1;
p = head;
while(p->next && ((i --) > 0))
p = p->next;
/*i 超出线性表长度*/
if (i > 0)
return -1;
*value = p->data;
return 0;
}
/*删除 节点i, 当i = 1时,即删除头部节点*/
int deleteList(chainNode_p head, int i)
{
chainNode_p p;
if (head == NULL)
return -1;
p = head;
while(p->next && ((--i) > 0))
p = p->next;
if (i > 0 || p->next == NULL)
return -1;
free(p->next);
p->next = p->next->next;
return 0;
}
/*删除尾部节点*/
int deleteListOfTail(chainNode_p head)
{
chainNode_p p;
if (head == NULL)
return -1;
p = head;
while(p->next->next && (p = p->next));
p->next = NULL;
free(p->next);
return 0;
}
/*释放线性表*/
int freeList(chainNode_p head)
{
chainNode_p p1, p2;
if (head == NULL)
return -1;
p1 = head->next;
while(p1) {
p2 = p1->next;
free(p1);
p1 = p2;
}
free(head);
return 0;
}
int printfList(chainNode_p head)
{
chainNode_p p;
p = head->next;
while(p) {
printf("%d\t", p->data);
p = p->next;
}
printf("\n");
return 0;
}
#if 1
/*测试*/
int main()
{
int i;
ElemType e ;
chainNode_p head = NULL ;
initList(&head);
for(i = 0; i < 10; i++)
insertList(head, i, i+1);
printfList(head);
getValueList(head, &e, 1);
printf("head: %d\n", e);
getValueList(head, &e, 6);
printf("list_6: %d\n", e);
addListOfHead(head, 0);
addListOfTail(head, 10);
printfList(head);
deleteListOfTail(head);
printfList(head);
deleteList(head, 5);
printfList(head);
freeList(head);
return 0;
}
#endif
三、分析
我们发现无论是单链表插入还是删除算法,它们其实都是由两个部分组成:第一部分就是遍历查找第i个元素,第二部分就是实现插入和删除元素。从整个算法来说,我们很容易可以推出它们的时间复杂度都是O(n)。
再详细点分析:如果在我们不知道第i个元素的指针位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储结构是没有太大优势的。但如果,我们希望从第i个位置开始,插入连续10个元素,对于顺序存储结构意味着,每一次插入都需要移动n-i个位置,所以每次都是O(n)。
而单链表,我们只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来只是简单地通过赋值移动指针而已,时间复杂度都是O(1)。显然,对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显啦~
四、对比
单链表结构与顺序存储结构优缺点比较,我们分别从存储分配方式、时间性能、空间性能三方面来做对比。
-
存储分配方式:
顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素 -
时间性能:
查找
顺序存储结构O(1)
单链表O(n)
插入和删除
顺序存储结构需要平均移动表长一半的元素,时间为O(n)
单链表在计算出某位置的指针后,插入和删除时间仅为O(1) -
空间性能:
顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易发生溢出。
单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
五、总结
若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。
若需要频繁插入和删除时,宜采用单链表结构。
比如说游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。
而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,此时再用顺序存储就不太合适了,单链表结构就可以大展拳脚了。
当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。
&emsp:而如果事先知道线性表的大致长度,比如一年12个月,一周就是星期一至星期日共七天,这种用顺序存储结构效率会高很多。
关注公众号"小败日记",搬砖过程遇到的问题,大家一起探讨,资源共享