说明:
学习内容根据b站王卓老师视频学习记录
参考地址第02周08--2.4线性表的顺序表示和实现3_哔哩哔哩_bilibili
目录
1. 线性表
顺序表
在写代码之前可以做一些简单的定义(方便解读)
#define LIST_MAX 100 //多项式可能达到的最大长度
#define OVERFLOW -2
#define OK 1
#define ERROR 0
typedef int Status;
typedef char ElemType;
存储结构:
1.静态定义:
//确定存储空间(静态分布) typedef struct{ int elem[LIST_MAX]; //存储存储地址 int length; //线性表的当前长度 }SqList; //顺序表类型
2.动态定义
//指针的形式(动态分布) typedef struct{ int *elem; //指向存储地址 int length; //顺序表的当前长度 }SqList; //顺序表类型
动态定义因为没有直接定义线性表的存储空间,所以在使用时(初始化时要动态分配存储空间)引用如下形式的代码:
SqList L; L.data = (ElemType *)malloc(sizeof(ElemType)*LIST_MAX);
补充:
多项式非零项的定义(动态分布)://多项式非零项的定义 typedef struct{ float p; //系数 int e; //指数 }Polynomial; typedef struct{ Polynomial *elem; //存储空间的基地址 int length; //当前项的个数 }SqList; //多项式的顺序存储结构类型为SqList
初始化:(以动态分布为例子)
//线性表的初始化(参数引用引用) Status InitList(SqList &L){ //构建一个空的顺序表 L.elem = new int[LIST_MAX]; if (!L.elem) return OVERFLOW; L.length = 0; return OK; }
顺序表的基本简单操作:
(销毁、清空、求长度、判断是否为空)的相关操作//销毁线性表L void DestoryList(SqList &L){ if (L.elem) delete L.elem; //释放存储空间 } //清空线性表 void ClearList(SqList &L){ L.length = 0; //将线性表的长度置为0 } //求线性表的长度 int GetLength(SqList &L){ return (L.length); } //判断线性表L是否为空 int IsEmpty(SqList &L){ if(L.length == 0){ return 1; } else{ return 0; } }
顺序表的基本操作:
顺序表的取值:
在顺序表L中获取i位置的数据,并用e来返回获取到的元素;//线性表(顺序表)的取值(根据位置i获取相应位置数据元素的内容) int GetElem(SqList L,int i,ElemType &e){ if (i<1 || i>L.length) return ERROR; //判断i位置是否合理,不合理返回ERROR e = L.elem[i-1]; //第i-1个位置存储着第i个数据 return OK; }
顺序表的查找:
查找顺序表L中数据和e相等的元素,如果查找成功则返回其序号,若没有查找 到则返回0;//顺序表的查找 int LocateElem(SqList L,ElemType e){ //在线性表L中查找值为e的数据元素,返回其序号(是第几个元素) for (int i = 0; i < L.length; ++i) { if (L.elem[i] == e) return i+1; //查找成功,返回序号 return 0; //查找失败,返回0 } }
顺序表的插入:
在顺序表L中第i位置处插入数据e;//顺序表的插入 Status ListInsert(SqList &L,int i,ElemType e){ if(i<1 || i>L.length+1) return ERROR; //i值不合法 if (L.length == LIST_MAX) return ERROR; //当前存储空间已经满了 for (int j=L.length-1; j>=i-1; ++j) { L.elem[j+1] = L.elem[j]; //插入位置及之后的元素后移 } L.length++; //表长增加1 return OK; }
顺序表的删除:
在顺序表L中删除第i位置处的元素;//线性表的删除 Status ListDelete(SqList &L,int i){ if (i<1 || (i>L.length)){ return ERROR; //i值不合法 } for (int j=i; j<=L.length; ++j) { L.elem[j-1] = L.elem[j]; //被删除的元素之后的元素前移 } L.length--; //表长减1 return OK; }
链表
单链表
理解概念:节点只有一个指针域的链表。
无头节点时空表:头指针指向空;
有头节点的空表:头节点的指针域为空。
头节点的好处:在插入、删除等操作时不需要特殊处理。
顺序表采用的是随机存取,链表采用的是顺序存取。
存储结构
//单链表的存储结构
typedef struct Lnode{
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
}Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型
使用*LinkList的好处:在定义链表时可以直接定义LinkList L,等价与Lnode *L;
同时在定义结点指针p时:可以直接写LinkList p,等价于Lnode *p;
初始化:
建立一个空表
//单链表的初始化
Status InitList(LinkList &L){
L = new Lnode; //c++语言
//或L = (LinkList)malloc(sizeof(Lnode)); //c语言
L->next = NULL;
return OK;
}
单链表的基本操作的实现:
判断链表是否为空:
如果为空就返回1,不为空返回0。//判断链表是否为空 int ListEmpty(LinkList L){ if (L->next){ //非空 return 0; } else{ return 1; } }
链表的销毁:
从头结点开始依次释放所有结点。//单链表的销毁 Status DestroyList(LinkList &L){ Lnode *p; //或LinkList p; while (L){ p = L; L = L->next; delete p; } return OK; }
清空单链表:
链表中除头指针和头节点其他清空。//清空单链表 Status ClearList(LinkList &L){ Lnode *p,*q; //或LinkList p,q; p = L->next; while (p){ //没到表尾 q = p->next; delete p; p = q; } L->next = NULL; //头节点指针域为空 }
求单链表的表长:
从首元节点开始,依次计数所有的结点。//求单链表的表长 Status ListLength(LinkList L){ Lnode *p; //或LinkList p; int count = 0; //记录长度 p = L->next; while(p){ //遍历求单链表的长度 count++; p = p->next; } return count; }
取值:
取单链表中第i位置的元素并用e返回。//取值 Status GetElem(LinkList L,int i,ElemType &e){ Lnode *p; p = L->next; //初始化 int j=1; while (p && j<i){ //向后扫描,直到p指向第i个元素或p为空 p = p->next; j++; } if (!p || j>i){ //第i个元素不存在 return ERROR; } e = p->data; //取第i个元素 return OK; }
查找:
1.根据指定的数据获取该数据所在的位置(地址)。//查找 Lnode *LocateList(LinkList L,ElemType e){ Lnode *p; p = L->next; while(p && p->data != e){ p = p->next; } return p; }
2.根据指定数据获取该数据位置序号。
//查找(序号) int LocateList(LinkList L,ElemType e){ Lnode *p; p = L->next; int i = 1; while(p && p->data!=e){ p = p->next; i++; } if (p){ //在单链表中找到e return i; } else{ //在单链表中没有找到(失败) return 0; } }
插入:
在第i个结点前插入值为e的结点。Status ListInsert(LinkList &L,int i,ElemType e){ Lnode *p; p = L; int j = 0; //用来记录指针的位置 while (p && j<j-1){ //循环寻找第i-1个结点,并使p指向i-1结点 p = p->next; j++; } if (!p || j>i-1) //i大于表长+1或者小于1,插入位置非法 return ERROR; else{ Lnode *s = new Lnode; //建立新节点 s->data = e; s->next = p->next; p->next = s; return OK; } }
删除:
删除第i个结点。//删除 Status DeleteList(LinkList &L,int i,ElemType &e){ Lnode *p; p = L; //p指向链表L的头结点 int j = 0; while(p->next && j<i-1){ //寻找第i个结点,并使p指向其前驱 p = p->next; j++; } if (!(p->next) || j>i-1) //位置不合理 return ERROR; else{ Lnode *q = p->next; //创建q指针指向p->next;方便表示p->next->next p->next = q->next; e = q->data; delete q; //释放删除结点的空间 return OK; } }
单链表的建立:
头插法:元素插入到链表的头部//链表的建立(头插法) void CreateList(LinkList &L,int n){ L = new Lnode; //或L = (LinkList)malloc(sizeof(Lnode)) L->next = NULL; //先建立一个带头结点的单链表 for (int i = n; i > 0; --i) { Lnode *p = new Lnode ; //生成新结点p=(Lnode *)malloc(sizeof(Lnode(( cin>>p->data; //输入元素值 scanf(&p->data);(c语言) p->next = L->next; //插入到表头 L->next = p; } }
尾插法: 元素插入在链表尾部。
//链表的建立(头插法) void CreateList(LinkList &L,int n){ L = new Lnode; //或L = (LinkList)malloc(sizeof(Lnode)) L->next = NULL; //先建立一个带头结点的单链表 for (int i = n; i > 0; --i) { Lnode *p = new Lnode ; //生成新结点p=(Lnode *)malloc(sizeof(Lnode(( cin>>p->data; //输入元素值 scanf(&p->data);(c语言) p->next = L->next; //插入到表头 L->next = p; } }
循环链表:一种头尾相连的链表(最后一个结点的指针指域向头结点)
判断链表为空:判断最后一个结点的指针域是否指向头结点。(p->next!=L)
带尾指针循环链表的合并:
//循环链表的合并
//假设Ta、Tb都是非空的单循环链表
LinkList Connect(LinkList Ta,LinkList Tb){
Lnode *p = Ta->next; //p存表头结点
Ta->next = Tb->next->next; //Tb表头连接Ta表尾
delete Tb->next; //释放Tb表头结点
Tb->next = p; //修改指针
return Tb;
}
双链表
理解概念:节点有两个指针域的链表。
存储结构:
//双向链表结构定义
typedef struct DuLNode{
ElemType data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
在双链表中:p->next->prior = p = p->prior->next;
双向链表的插入:
在带头结点的双向循环链表L中第i位置之前插入元素e。
//双向链表的插入
//在带头结点的双向循环链表L中第i位置之前插入元素e
Status ListInsert(DuLinkList &L,int i,ElemType e){
DuLNode *p;
if (!(p=GetElem(L,i))) //GetElem(L,i)得到双向链表的第i个位置元素的地址(伪代码)
return ERROR;
DuLNode *s = new DuLNode;
s->data = e;
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
return OK;
}
双向链表的删除:
删除带头结点的双向链表L的第i个元素,并用e返回。
//双向链表的删除
//删除带头结点的双向链表L的第i个元素,并用e返回。
Status ListDelete(DuLinkList &L,int i,ElemType &e){
DuLNode *p;
if(!(p = GetElem(L,i))) //GetElem(L,i)得到双向链表的第i个位置元素的地址(伪代码)
return ERROR;
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
delete(p);
return OK;
}
线性表的应用
线性表的合并:(无序)
步骤:
-
分别获取LA表以及LB表的长度n,m。
-
从LB中第一个数据开始,循环n次执行以下操作:
-
从LB中查找第i个数据元素赋值给e;
-
在LA中查找元素e,如果不存在,将e插在表LA后。
-
//线性表的合并
void Union(SqList &La,SqList &Lb){
int La_len = GetLength(La);
int Lb_len = GetLength(Lb);
ElemType e;
for (int i = 1; i <= Lb_len; ++i) {
GetElem(Lb,i,e);
if (!LocateElem(La, e)) {
ListInsert(La, ++La_len, e);
}
}
}
有序表的合并:
顺序表实现:(代码在定义指针时会有报错,当然也可以不用指针去做)
算法步骤:
-
创建一个表长为m+n的空表LC
-
指针
pc
初始化,指向LC的第一个元素。 -
指针
pa
h和pb
初始化分别指向LA和LB的第一个元素。 -
当指针
pa
和pb
均未到达相应表尾时,则依次比较pa
和pb
所指向的元素值,从LA或LB摘取元素值较小的节点插入LC的最后。 -
如果
pb
已将到达LB的表尾,依次将LA的剩余元素插入LC的最后。 -
如果
pa
已将到达LA的表尾,依次将LB的剩余元素插入LC的最后。
//有序表的合并——顺序表实现
void MergeList(SqList LA,SqList LB,SqList &LC){
SqList *pa,*pb,*pc,*pa_last,*pb_last; //创建几个指针(此处为自己创建,下文会有报错,原视频无定义)
pa = LA.elem;
pb = LB.elem; //指针pa,pb的初始值分别指向两个表的第一个元素
LC.length = LA.length + LB.length;
LC.elem = new ElemType[LC.length]; //为合并后的新表分配一个数组空间
pc = LC.elem; //指针pc指向新表的第一个元素
pa_last = LA.elem + LA.length-1; //指针pa_last指向LA表的最后一个元素
pb_last = LB.elem + LB.length-1; //指针pb_last指向LB表的最后一个元素
while (pa<=pa_last && pb<=pb_last){ //两个表都为非空
if (*pa<=*pb) //依次“摘取”两表中值较小的结点
*pa++=*pa++;
else
*pc++ =*pb++;
}
while (pa<= pa_last)
*pa++ = *pa++; //LB表已经到达表尾,将LA中剩余元素加入LC
while (pb<=pb_last) //LA表已经到达表尾,将LB中剩余元素加入LC
*pc++ = *pb++;
}
有序表合并:用链表实现
(相比顺序表的指针创建相对简单的多)
//有序表的合并——用链表实现
void MergeList(LinkList &La,LinkList &Lb,LinkList &Lc){
Lnode *pa,*pb,*pc;
pa = La->next;
pb = Lb->next;
pc = Lc = La; //用La的头结点作为Lc的头结点
while(pa && pb){ //谁小谁在前
if (pa->data <=pb->data){
pc->next = pa;
pc = pa;
pa = pa->next;
} else{
pc->next = pb;
pc = pb;
pb = pb->next;
}
pc->next = pa?pa:pb; //插入剩余段
delete Lb; //释放Lb的头结点
}
}
第一章完结