第二章 线性表
2.1 线性表定义和基本操作
定义
相同数据类型(每个数据一样大)的n个元素的有限序列(有次序)
每个元素有前驱(除第一个)/后继(除最后一个)
基本操作
(创销增删改查)
2.2 线性表顺序表示
一位数组可以静态分配(大小和空间实现固定),也可动态分配(占满后开辟一块)
顺序表特点
- 随机访问,通过首地址和元素序号(连续存放)可在O(1)内找到指定元素
- 储存密度高,只存数据元素
- 逻辑上相邻的元素物理上也相邻,所以插删需移动大量元素
基本操作
插入
将顺序表第i个元素及其后元素后移一位(别忘了更改length)
最好情况:表位插入O(1);最坏情况:O(n);平均情况:O(n)
按值查找(顺序查找)
最好情况:恰在表头O(1);最坏情况:O(n);平均情况:O(n)
线性表的链式表示
单链表
结点分为:指针域 数据域
头指针标识一个单链表,头指针为null时表示一个空表。为操作方便,在第一个结点之前附加一个头结点
头结点
头指针始终指向第一个结点(此时为头结点),头结点一般不储存信息
优点
- 第一个位置和其他位置操作统一。链表第一个位置上的操作和其他位置一致,不需要单独处理(头结点不储存信息,不用单独管他)
- 空表和非空表处理得到统一。因为无论是否为空表,都有一个头结点,头指针一直有所指
按序号查找表结点
从第一个节点顺着next域往下找,直到第i个,否则返回最后一个结点指针域null
while(p && j<i) # 找到i时,或到最后时停止
按值查找表结点
while(p!=NULL && p->data!=e)
# 若没找到,且没到最后,继续找。到最后p=NULL,即尾结点后面的空结点
插入结点(后插)
找到前驱(i-1)为*p,令新结点*s指针域指向*p后继结点,再令*p指针域指向*s
p = GetElem(L, i-1); // 查找插入位置的前驱
s->next = p->next;
p->next = s;
最后两步颠倒的话:丢失了原有的p->next,即后继结点的位置
前插
指在一个结点前面插入。
一种方式是找到i-1转化为后插
一种是将*s插入到*p后面,然后将s与p的data交换(李代桃僵)
头插法建立单链表
从空表开始生成新结点,然后将新结点插入当前链表表头,即头结点之后
L->next = NULL;
...
s->next = L->next; // s新插入相当于占住L(头结点)的位置
L->next = s; // L由占到s前面,重复成为头结点
尾插法建立单链表
增加一个尾指针r,始终指向尾结点
删除结点
设*p是被删的前驱,修改*p的指针域指向*q的下一个结点,主要耗费在查找,O(n)
扩展:删除结点*p
将后继结点的值赋给自身,然后删除后继结点(李代桃僵)
双链表
结点类型
数据域,指针域:前驱prior 后继next
可以很方便的找到前驱结点
插入
在p所指结点后面插入*s
s->next = p->next; // s的后继指向p的后面
p->next->prior = s; // 后继结点的前驱为s
s->prior = p; // s的前驱
p->next = s; // p的后继结点设置
可以画示意图
删除
循环链表
循环单链表
表尾结点*r的next指向L
判断是否为空:L->next = L,头结点的指针为头指针
单链表只能从表头开始遍历,而循环单链表可以从任意位置,对于表头和表尾的操作只需要O(1)
循环双链表
为空时,头结点的prior next 都指向L
静态链表
借助 数组 描述线性表,结点也有数据域和指针域,指针是结点相对地址,又称游标
静态链表需要预先分配一块连续内存空间
以next==-1结束,插入删除与动态链表相同,只需要修改指针,不移动元素(看图)
顺序表与链表比较
1 存取方式
顺序表可以顺序存取,也可随机存取(下标)(存取只需1次);链表只能从表头顺序存取(存取需i次)
2 逻辑结构物理结构
都是线性结构,顺序存取时,逻辑上相邻的元素,物理上也相邻
3 查找、插入、删除
按值查找,顺序表无序时,都为O(n);有顺序时,为O(log2n)(折半查找)
按序号,顺序表随机访问O(1),链表O(n);
插入删除,顺序表需要移动大量元素
4 空间分配
顺序:需要预先分配足够大空间
链式:灵活高效,但储存密度低(有指针)