文章目录
第1章 线性表
定义
基本操作
InitList(&L)
:初始化表。构造一个空的线性表,分配内存空间。
DestroyList(&L)
:销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
ListInsert(&L,i,e)
:插入操作。在表L中的第i个位置上插入指定元素e。
ListDelete(&L,i,&e)
:删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
GetElem(L,i)
:按位查找操作。获取表L中第i个位置的元素的值。
LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素,并返回其位序。
其他常用操作:
Length(L)
:求表长。返回线性表L的长度,即L中数据元素的个数。
PrintList(L)
:输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L)
:判空操作。若L为空表,则返回true,否则返回false。
Tips:什么时候要传入引用&
——对参数的修改结果需要“带回来”。
实例——归并有序线性表
/*题目背景:已知线性表LA和LB中的数据元素按值非递减有序排列,归并LA和LB成为LC,并且LC中的数据元素仍按值非递减有序排列
LA = [3,5,8,10] LB = [2,6,8,9,11,15]
则 LC = [2,3,5,6,8,8,9,10,11,15]
算法思想:设LC为空表,然后将LA和LB中的元素逐个插入到LC中,为将LC中元素按值非递减有序排列,可设两个指针i和j分别指向LA和LB中某个元素,若设i当前指向的元素为a,j当前所指元素为b,则当前插入到LC中的元素c为小的元素值。
*/
void MergeList(List LA,List LB,List &LC) {
InitList(&LC);
int i,j,k = 0;
i = j = 1;
Length_A = LA.length;
Length_B = LB.length;
while((i < = Length_A) && (j <= Length_B)) { //LA和LB都非空
GetElem(LA,i,a);
GetElem(LB,j,b);
if(a <= b) {
ListInsert(&LC,++k,a);
++i;
}
else{
ListInsert(&LC,++k,b);
++j;
}
}
while(i <= Length_A) { //插入LA的剩余元素
GetElem(LA,i++,a);
ListInsert(&LC,++k,a);
}
while(j <= Length_B) { //插入LB的剩余元素
GetElem(LB,j++,b);
ListInsert(&LC,++k,b);
}
}
*顺序表
定义
特点
- 随机访问。可以在O(1)时间内找到第i个元素。
- 存储密度高。每个节点只存储数据元素。
- 拓展容量不方便。
- 插入、删除数据元素不方便。
实现方式
静态分配
#define MaxSize 10
typedef struct {
ElemType data[Maxsize]; //用静态的“数组”存放数据元素
int length; //顺序表当前长度
}SqList;
Q:“数组”存满了怎么办?
A:因为存储空间是静态的,顺序表的表长刚开始确定后就无法更改。
Q:如果一开始就声明很大的内存空间呢?
A:浪费内存资源!
动态分配
#define InitSize 10 //顺序表初始长度
typedef struct {
ElemType *data; //指示动态分配数组的指针
int MaxSize; //顺序表最大容量
int length; //顺序表当前长度
}SqList;
Key:动态申请和释放内存空间——malloc()
、free()
L.data = (ElemType* )malloc(sizeof(ElemType) * InitSize);
malloc()
返回的存储空间起始地址要转换为与数据元素的数据类型相对应的指针。
基本操作——插入
ListInsert(&L,i,e)
:插入操作。在表L的位序 i 处插入指定元素e。
#define MaxSize 10
typedef struct {
ElemType data[Maxsize]; //用静态的“数组”存放数据元素
int length; //顺序表当前长度
}SqList;
bool ListInsert(SqList &L,int i,int e) {
if(i < 1 || i > L.length + 1) //判断i的范围是否有效
return false;
if(L.length >= Maxsize)
//判断存储空间是否已满,另一种方法是利用realloc()增加分配地址空间
return false;
for(int j = L.length;j >= i; j--)
L.data[j] = L.data[j - 1];
L.data[i - 1] = e;
L.length ++;
return true;
}
平均时间复杂度O(n)
### 基本操作——删除
ListDelete(&L,i,&e)
:删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
bool ListDelete(SqList &L,int i,int &e) {
if(i < 1 || i > L.length + 1) //判断i的范围是否有效
return false;
e = L.data[i - 1]; //被删除的元素赋值给e
for(int j = i;j < L.length; j++)
L.data[j - 1] = L.data[j];
L.length --;
return true;
}
平均时间复杂度O(n)
### 基本操作——按位查找
GetElem(L,i)
:按位查找操作。获取表L中第i个位置的元素的值。
ElemType GetElem(SqList L,int i) {
return L.data[i - 1];
}
时间复杂度O(1)
### 基本操作——按值查找
LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素,并返回其位序。
int LocateElem(SqList L,int e) {
for(int i = 0;i < L.length; i++)
if(L.data[i] == e)
return i + 1;
return 0;
}
平均时间复杂度O(n)
*单链表
定义
强调单链表——LinkList
强调结点—— LNode *
基本操作——初始化
typedef struct LNode{
ElemType data; //每个结点存放的数据元素
struct LNode *next; //指针指向下一个结点
}LNode,*LinkList;
//初始化不带头的单链表
bool InitList(LinkList &L) {
L = NULL; //空表,暂时没有任何结点(防止脏数据)
return true;
}
//初始化带头结点的单链表
bool InitList(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点 !不存储数据!
if(L == NULL) //分配失败
return false;
L->next = NULL; //头结点后暂时没有其他结点
return true;
}
int main(){
LinkList L; //声明一个指向单链表的指针
InitList(L);
……
}
基本操作—— 插入删除
ListInsert(&L,i,e)
:插入操作。在表L的位序 i 处插入指定元素e。
/*按位序插入*/
//带头结点
bool ListInsert(LinkList &L, int i, ElemType e) {
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存储数据)
while(p != NULL && j < i - 1) { //循环找到第 i-1 个结点
p = p->next;
j++
}
if(p == NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//不带头结点
bool ListInsert(LinkList &L,int i,ElemType e) {
if(i < 1)
return false;
if(i == 1){ //插入第一个结点的操作与其他结点操作不同
LNode *s = (LNode*) malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode *p; //指针p指向当前扫描到的结点
//int j = 0
int j = 1; //当前p指向的是第几个结点(p指向第一个结点!!!)
p = L;
while(p != NULL && j < i - 1) { //循环找到第 i-1 个结点
p = p->next;
j++
}
if(p == NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
/* 指定结点的后插操作 */
bool InsertNextNode(LNode *p,ElemType e) {
if(p = NULL)
return false;
LNOde *s = (LNode*)malloc(sizeof(LNode));
if(s == NULL)
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
/* 指定结点的前插操作 */
bool InsertPriorNode(LNode *p,ElemType e) {
if(p = NULL)
return false;
LNOde *s = (LNode*)malloc(sizeof(LNode));
if(s == NULL)
return false;
s->next = p->next;
p->next = s; //连接新结点s到p之后
s->data = p->data; //将p中元素复制到s中
p->data = e; //p中的元素覆盖为e
return true;
}
ListDelete(&L,i,&e)
:删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
/* 按位删除 */
//带头结点
bool ListDelete(LinkList &L, int i, ElemType &e) {
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存储数据)
while(p != NULL && j < i - 1) { //循环找到第 i-1 个结点
p = p->next;
j++
}
if(p == NULL) //i值不合法
return false;
if(p->next == NULL) //第i - 1个结点后已无其它结点
return false;
LNode *q = p->next; //q指向被删除结点
e = q->data; //用e返回元素值
p->next = q->next; //将q结点从链中断开
free(q); //释放结点存储空间
return true;
}
/* 指定结点的删除操作 */
bool DeleteNode(LNode *p) {
if(p == NULL)
return false;
LNode *q = p->next; //q指向p的后继结点
/*如果此时p已经是最后一个结点,则q为NULL;
删除结点的方式只能是从表头开始依次寻找p的前驱;
时间复杂度为O(n)*/
p->data = q->data; //和后继结点交换数据域
p->next = q->next; //将q结点从链中断开
free(q); //释放后继结点存储空间
return true;
}
单链表的局限性:无法逆向检索
基本操作——查找
GetElem(L,i)
:按位查找操作。获取表L中第i个位置的元素的值。
LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素,并返回其位序。
/* 按位查找 */
//带头结点
LNode* GetElem(LinkList L,int i) {
if(i < 0)
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存储数据)
while(p != NULL && j < i) { //循环找到第 i 个结点
p = p->next;
j++
}
return p;
}
/* 按值查找 */
//带头结点
LNode* LocateElem(LinkList L,ElemType e) {
LNode *p = L->next; //指针p指向当前扫描到的结点(第一个结点)
while(p != NULL && p->data != e) {
p = p->next;
}
return p;
}
基本操作——计算表的长度
Length(L)
:求表长。返回线性表L的长度,即L中数据元素的个数。
int Length(LinkList L) {
int len = 0;
LNode *p = L;
while(p->next != NULL){
p = p->next;
len++;
}
return len;
}
基本操作——单链表的建立
/* 尾插法建立单链表 */
//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType e) {
if(i < 1)
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存储数据)
while(p != NULL && j < i - 1) { //循环找到第 i - 1个结点
p = p->next;
j++
}
if(p == NULL) //i值不合法
return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
/* 头插法建立单链表 */ //——链表逆置(头插法)
//后插操作:在头结点后插入新的数据元素e
bool InsertNextNode(LNode *p,ElemType e) {
if(p = NULL)
return false;
LNOde *s = (LNode*)malloc(sizeof(LNode));
if(s == NULL)
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
Tips :头插法、尾插法核心就是初始化操作、指定结点的后插操作。
头插法的重要应用:链表的逆置
双链表
基本操作——初始化
typedef struct DNode {
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinkList;
//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L) {
L = (DNode*)malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL)
return false;
L->prior = NULL;
L->next = NULL;
return true;
}
基本操作——插入
//在p结点后插入s结点
bool InsertNextDode(DNode *p,DNode *s) {
if(p == NULL || s == NULL)
return false;
s-next = p->next;
if(p->next != NULL) //如果p有后继结点
p->next->proir = s;
s->prior = p;
p->next = s;
return true;
}
基本操作——删除和销毁
//删除p结点的后继结点
bool DeleteNextDode(DNode *p) {
if(p == NULL)
return false;
DNode *q = p->next;
if(q == NULL) //如果p没有后继结点
return false;
p->next = q->next;
if(q->next != NULL) //q结点不是最后一个结点
q->next->prior = p;
free(q);
return true;
}
/* 销毁双链表 */
void DestroyList(DLinkList &L) {
while(L->next != NULL) //每次删除头结点后的数据
DeleteNextDNode(L);
free(L); //释放头结点
L = NULL;
}
循环链表
表尾结点的next指针指向头结点
//循环单链表 !(循环双链表类似) !
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L) {
L = (LNode*)malloc(sizeof(LNode)); //分配一头结点
if(L == NULL)
return false;
L->next = L; //头结点next指向头结点
return true;
}
//判断循环单链表是否为空
bool Empty(LinkList L) {
if(L->next == L)
return true;
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode* p){
if(p->next == L)
return true;
return false;
}
静态链表
用数组的方式实现。优点:增删操作不需要大量移动元素; 缺点:不能随机存取,只能从头结点开始依次往后查找,容量固定不可变。
插入位序为i的结点:
① 找到一个空的结点,存入数据元素(eg:初始化为-2)
② 从头结点出发找到位序为 i-1的结点
③ 修改新结点的 next
④修改 i-1 号结点的 next
删除某个结点:
①从头结点出发找到前驱结点
②修改前驱结点的游标
③被删除结点 next 设为 -2
小结