数据结构——顺序表与链表

今天开始总结一下线性表这一章的内容。



线性表

首先来说一下分类

        |- 顺序存储 ———— 顺序表
        |
线性表---|             |- 单链表
        |             |- 双链表
        |- 链式存储 ---|- 循环链表
                      |- 静态链表

线性表:有相同数据类型、有限序列。
线性表的主要操作如下:

函数名实现操作
InitList(&L)初始化,构造一个空表
Length(L)求表长
LocateElem(L, e)按值查找
GetElem(L, i)按位置查找
ListInsert(&L, i, e)插入
ListDelete(&L, i, &e)删除
PrintList(L)输出所有元素
Empty(L)判断L是否为空
DestoryList(&L)销毁

一、顺序表

特点:随机存取,顺序存储
(易错点:顺序存取是一种读写方式,不是存储方式,有别于顺序存储。)
顺序表不用在节点中存放指针域,因此存储密度较大。

1. 顺序表定义

逻辑顺序与物理顺序相同
注意:线性表中元素位序从1开始,数组从0开始

//静态分配存储空间
# define MaxSize 50
typedef struct{
	ElemType data[MaxSize];
	int length;
}SqList;
//动态分配存储空间
# define InitSize 100
typedef struct{
	ElemType *data;		//指示动态分配数组的指针
	int MaxSize, length;	//数组的最大容量,当前个数
}SqList;
//初始动态分配语句
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);

2. 插入

时间复杂度:O(n)
在L的第i个位置(1<=i<=L.length+1)插入新元素e
第i个元素开始及其后面的元素向右移动一个位置,腾出的空位放e
在第 i 个位置插入元素,需要移动 n-i+1 个元素。

//判断i位置合法
//判断数组是否满
for(j = L.length; j >= i; j--){ //后移元素
	L.data[j] = L.data[j - 1];
} 
L.data[i - 1] = e;
L.length++;

3. 删除

时间复杂度:O(n)
删除L的第i个元素
将第i个位置的元素赋值给e,再将第i个位置之后的所有元素前移
删除第 i 个元素,需要移动 n-i 个元素。

//判断i位置合法
//判断数组是否空
e = L.data[j - 1];
for(j = i; j <= L.length+1; j++{
	L.data[j-1] = L.data[j];
}
L.length--;

4. 按值查找

时间复杂度:O(n)
找到L中第一个值为e的元素,然后返回其位置
(若小标为i的元素值为e,则返回其位序 i+1)

for(j = 0; j <= L.length+1; j++){
	if(L.data[j] == e){
		return j+1;
	}
	else{
		return -1;
	}
}

二、单链表

1. 定义

不需要使用地址连续的存储单元
在这里插入图片描述

//单链表中节点类型定义
typedef struct LNOde{
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;
  • 通常用“头指针”来标识单链表
    如单链表L,头指针为NULL时,表示一个空表。
    头指针始终指向链表的第一个结点,与是否带头结点无关
  • 在单链表之前附加一个结点,称为“头结点
    头结点的指针域指向线性表的第一个结点,数据域可以不设任何信息,也可以记录表长等信息。
  • 头结点,优点:
    (1)开始结点的位置存在头结点的指针域,所以在链表第一个位置上的操作和其他位置操作一致,无需特殊处理。
    (2)无论链表是否空,头指针都是指向头结点的非空指针,所以空表和非空表的处理就一致了。
    在这里插入图片描述

2. 头插建立单链表

时间复杂度:O(n)
从空表开始,生成一个新的结点,并将读取的数据存放到新的结点,然后将新结点插入到当前链表的表头。
头插法生成的链表,读入数据的顺序与生成链表中元素的顺序是相反的。
在这里插入图片描述

while(x != 9999){
	s->data = x;
	s->next = L->next;
	L->next = s;
}

3. 尾插建立单链表

时间复杂度:O(n)
将新结点插入到当前链表的表尾。需要增加一个尾指针r,r始终指向当前链表的尾结点。
在这里插入图片描述

while(x != 9999){
	s->data = x;
	r->next = s;
	r = s;
}
r->next = NULL;	//尾结点指针置空

4. 按序号查找

时间复杂度:O(n)

LNode *p = L->next; 	//头结点指针赋给p
if(i == 0){
	return L;
}
if(i < 1){
	return NULL;
}
while(p && j<i){	//p不为空,且j<i
	p = p->next;
	j++;
}

5. 按值查找

时间复杂度:O(n)

LNode *p = L->next;
while(p != NULL && p->data != e){
	p = p->next;
}

6. 插入结点

算法的时间主要消耗在找 i-1 ,时间复杂度为O(n),如果在给定结点后面插入,则时间复杂度为O(1)
将值为x的元素插入到单链表的 i个位置上。
先找到待插入位置的前驱结点(i-1),在其后面插入新结点。
这样的操作方式又称为:后插操作(常用)
在这里插入图片描述

p = GetElem(L, i - 1);
s->next = p->next;
p->next = s;

前插操作:将 *s 插入到 *p 前面(可以将其转化为后插操作)
解决思路:仍然将 *s 插入到 *p 后面,然后将p->data与s->data交换即可。
时间复杂度O(1)

s->next = p->next;
p->next = s;
temp = p->data;
p->data = s->data;
s->data = temp;

7. 删除结点

时间复杂度:O(n),和插入一样,时间消耗在查找。
删除链表的第 i 个结点。首先要找到第 i-1 个结点,即被删结点的前驱,再将其删除。
在这里插入图片描述

p = GetElem(L, i-1);
q = p->next;
p->next = q->next;
free(q);

8. 求表长

时间复杂度:O(n)
从第一个结点开始依次访问,每访问一个结点,计数器加1,直到访问到空结点为止。
注意:单链表的长度不包括头结点,因此带头结点和不带头结点求表长操作不同。不带头结点的,当表为空时,要单独处理。


三、双链表

单链表只能从前向后遍历,如果要访问某个结点的前驱,只能从头遍历,时间复杂度O(n),所以引入了双链表。
双链表有两个指针:prior 和 next
在这里插入图片描述

typedef struct DNode{
	ElemType data;
	struct DNode *prior, *next;
}DNode, *DLinkList;

1. 插入

时间复杂度:O(1)
在 p 所指的结点之后插入结点 *s.
在这里插入图片描述

s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;

上述代码顺序不唯一,但(1)(2)要在(4)之前。

2. 删除

时间复杂度:O(1)
删除结点 *p 的后继结点 *q.
在这里插入图片描述

p-next = q->next;
q->next->prior = p;
free(q);

四、循环链表

1. 循环单链表

表尾结点 *r 的 next 域指向L,故表中没有指针域为NULL的结点。
判空的条件不是头结点指针是否为空,而是它是否等于头指针。

2. 循环双链表

头结点的 prior 指针要指向表尾结点。
某结点 *p 为尾结点时,p->next==L; 当L为空表时,头结点的prior和next都等于L.


五、静态链表

借助数组来描述线性表的链式存储结构,结点也有data域和next域。
与链表指针不同的是:这里的指针是结点的相对地址(数组下标),又称为游标。
可以分配较大的空间。
静态链表以 next==-1 作为结束标志。静态链表的插入、删除与动态链表相同,只需要修改指针,不需要移动元素。

# define MaxSize 50
typedef struct{
	ElemType data;
	int next;
}SLinkList[MaxSize];

小结

1. 双链表和单链表的区别

双链表:

  • 执行按值查找、按位查找的操作和单链表相同
    执行插入、删除操作有较大的不同
  • 由于方便找到前驱,故插入、删除时间复杂度O(1).

2. 循环单链表和单链表的区别

循环单链表:

  • 最后一个结点的指针不是NULL,而是指向头结点,形成一个环。
    插入、删除操作与单链表几乎一样;(如果操作在表尾进行则有不同)
    在任何位置上的插入、删除操作都是一样的,不用判断是不是在表尾。
  • 单链表只能从表头结点开始往后顺序遍历链表,循环单链表可以从表中任一节点开始遍历链表。

3. 顺序表和链表的区别

顺序表链表
存取方式顺序存取或随机存取顺序存取
逻辑与物理结构逻辑上相邻的元素,物理上也相邻逻辑相邻,物理不一定相邻
按值查找无序:O(n) ;有序:O(log2 n)O(n)
按序号查找O(1)O(n)
插入、删除O(n),要移动元素O(n)(主要消耗在查找位置,单纯操作为O(1))
空间分配预先分配空间,不能扩充需要的时候申请分配,灵活高效

4.应用

  • 常用操作:在最后一个元素之后插入元素和删除第一个元素。
    应该用 仅有尾指针的单循环链表。(可表示队列,因为队列是表头删除、表尾插入)
  • 常用操作:在表尾插入结点和删除结点。
    应该用 带头结点的双循环链表
  • 常用操作:删除第一个元素,删除最后一个元素、第一个元素之前插入新元素、最后一个元素之后插入新元素。
    应该用:只有头结点指针 没有尾结点指针的循环双链表
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值