目录
- 前言
- Chapter 1 | 线性表
- Chapter 2 | 栈和队列
- Chapter 3 | 串、数组和广义表
- Chapter 4 | 树与二叉树
- Chapter 5 | 图
- Chapter 6 | 查找
- Chapter 7 | 排序
- 结语
前言
我是Luckyhorse5。
本文章是我在数据结构与算法课程中的学习记录。我将基于C、C++来实现数据结构。
本文章分为:线性表、栈和队列、串 数组 广义表、树、图、查找、排序共七部分。约为50000字。用时100天完成。
Chapter 1 | 线性表
1.1 线性表的定义和特点
定义
线性表:
由n(n>=0)个数据特性相同的元素构成的有限序列成为线性表。
线性表的长度、空表:
线性表中元素的个数n(n>=0)定义为线性表的长度,n=0时成为空表。
特点
- 存在唯一的一个被称作“第一个”的数据元素。
- 存在唯一的一个被称作“最后一个”的数据元素。
- 除第一个之外,结构中的每个数据元素均只有一个前驱。
- 除最后一个之外,结构中的每个元素数据均只有一个后继。
1.2 线性表的类型定义
线性表是一个很灵活的数据结构,它的长度可根据需要增长或者缩短,不仅可以进行访问,而且可以进行插入和删除等操作。
下面给出线性表的抽象数据类型定义:
ADT List {
数据对象:D = { ai | ai ∈ ElemSet , i = 1 , 2 , ... n }
数据关系:R = { < ai -1 , ai > | ai-1 , ai ∈ D , i = 2 , ... , n }
基本操作:
InitList (&L)
操作结果:构造一个空的线性表L。
DestroyList (&L)
初始条件:线性表L已存在。
操作结果:销毁线性表L。
ClearList (&L)
初始条件:线性表L已存在。
操作结果:将L重置为空表。
ListEmpty (L)
初始条件:线性表L已存在。
操作结果:若L为空表,则返回true,否则返回false。
ListLength (L)
初始条件:线性表L已存在。
操作结果:返回L中数据元素的个数。
GetElem (L , i , &e)
初始条件:线性表L已存在。且1 <= i <= ListLength(L)
操作结果:用e返回L中第i个元素的值。
LocateElem (L , e)
初始条件:线性表L已存在。
操作结果:返回L中第1个值与e相同的元素在L中的位置。若这样的数据元素不存在,则返回值为0.
PriorElem (L , cur_e , &pre_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回其前驱,否则操作失败。pre_e无定义。
NextElem (L , cur_e , &next_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的数据元素,且不是第一个,则用next_e返回其前驱,否则操作失败。next_e无定义。
ListInsert (&L , i , e)
初始条件:线性表L已存在。且1 <= i <= ListLength(L) + 1
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
ListDelete (&L , i)
初始条件:线性表L已存在。且1 <= i <= ListLength(L)
操作结果:删除L的第i个数据元素,L的长度减1。
TraverseList (L)
初始条件:线性表L已存在。
操作结果:对线性表L进行遍历,在遍历过程中对L的每个结点访问一次。
} ADT List
1.3 线性表的顺序表示和实现
1.3.1 线性表的顺序存储表示
顺序表的定义
线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常称这种存储结构的线性表为顺序表。
顺序表的特点
逻辑上相邻的数据元素,物理次序也是相邻的。
这也是优点:以物理位置相邻表示逻辑关系,这样任一元素均可随机存取。
顺序表的存储结构
顺序表的存储结构是随机存取的存储结构。
高级程序设计语言中的数组类型也有随机存取的特性,因此通常用数组来描述数据结构中的顺序存储结构。
在C语言中用一维数组来表示线性表
动态分配
typedef struct
{
ElemType *elem;//存储空间的基地址
int length;//当前长度
}SqList;//顺序表的结构类型为SqList
静态分配
#define MAXSIZE 100 //顺序表可能达到的最大长度
typedef struct
{
ElemType elem[MAXSIZE];
int length;
}SqList;
当然了,这两种分配方式是等价的。
1.3.2 顺序表中基本操作的实现
1.顺序表的初始化
Status InitList (SqList &L)
{
//构造一个空的顺序表L
L.elem = new ElemType[MAXSIZE] //为顺序表分配一个大小为MAXSIZE的空间
if(!L.elem)
exit(OVERFLOW); //存储分配失败退出
L.length = 0; //空表长度为0
return OK;
}
2.顺序表的取值
Status GetElem (SqList &L,int i,ElemType &e)
{
if (i < 1 || i > L.length)
return ERROR; //判断i值是否合理,若不合理返回ERROR。
e = L.elem[i-1]; //elem[i-1]单元存储第i个数据元素
return OK;
}
时间复杂度为O(1)
3.顺序表的查找
int LocateElem (SqList L,ElemType e)
{
//在顺序表L中查找值为e的数据元素,返回其序号
for (int i = 0;i < L.length;i++)
{
if (L.elem[i] == e)
return i+1; //查找成功,返回序号i+1
}
return 0;//查找失败,返回0
}
时间复杂度为O(n)
平均查找长度 (ASL)
在查找时,为确定元素在顺序表中的位置,需和给定值进行比较的数据元素个数的期望值成为查找算法在查找成功时的平均查找长度。
假设pi时查找第i个元素的概率,Ci为找到表中关键字与给定值已进行过比较的关键字个数,则在长度为n的线性表中,查找成功时的平均查找长度为:
假设每个元素查找概率相等,即pi = 1/n 就可以简化为
4.顺序表的插入
Status ListInsert (SqList &L,int i,ElemType e)
{
//在顺序表L中第i个位置之前插入新的元素e,i值的合法范围时i <= i <= L.length+1
if ((i < 1)||(i > L.length + 1))
return ERROR;//i值不合法
if (L.length == MAXSIZE)
return ERROR;//当前存储空间已满
for (int j = L.length;j >= i - 1;j--)
{
L.elem[j+1] = L.elem[j];//插入位置及之后的元素后移
}
L.elem[i-1] = e;//将新元素e放入第i个位置
++L.length; //表长加1
return OK;
}
时间复杂度为O(n)
5.顺序表的删除
Status ListDelete (SqList &L,int i)
{
//在顺序表L中删除第i个元素,i的合法范围是1 <= i <= L.length
if ((i < 1)||(i > L.length-1))
return ERROR;//i值不合法
for (int j = i;j <= L.length-1;j++)
{
L.elem[j-1] = L.elem[j];//被删除元素之后的元素前移
}
--L.length;//表长减1
return OK;
}
时间复杂度为O(n)
1.4 线性表的链式表示和实现
1.4.1 单链表基本操作的实现
1.单链表的初始化
Status InitList(LinkList &L)
{
//构造一个空的单链表L
L = (LinkList)malloc(sizeof(Lnode)); //生成新结点作为头结点,用头指针L指向头结点
L -> next = NULL; //头结点的指针域置空
return OK;
}
2.判断链表是否为空
int ListEmpty (LinkList L)
{
if (L->next)
return 0;
else
return 1;
}
3.单链表的销毁
Status DestroyList_L (LinkList &L)
{
LNode* p;
while(L)
{
p = L;
L = L->next;
free(p);
return OK;
}
}
4.清空单链表
依次释放所有结点并将头结点指针置空 (重置为空表)
Status ClearList (LinkList &L)
{
LNode* p;
LNode* q;
p = L -> next;//p指向首元结点
while(p)
{
q = p -> next;//q指向下一个结点
free(p);//把p指向的空间释放掉
p = q;//把q赋值给p,这样p就也指向了下一个结点
}
L->next = NULL;
return OK;
}
5.求链表的表长
从首元结点开始,依次计数所有结点
```cpp
int ListLength_L (LinkList L)
{
int i = 0;//计数
p = L->next;//p指向首元结点
while(p)
{
i++;//计数器加1
p = p -> next;//p指向下一个结点
}
return i;//返回计数器i的值
}
6.单链表的取值
Status GetElem (LinkList L,int i,ElemType &e)
{
//在头结点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
p = L->next;//初始化,p指向
j = 1;//计数器j初值赋1
while(p && j < i)//顺序链向后扫描,直到p为空或者p指向第i个元素
{
p = p -> next;//p指向下一个结点
++j;//计数器j相应加1
}
if (!p || j > i)//i值不合法i>n或i<=0
return ERROR;
e = p->data;//取第i个结点的数据域
return OK;
}
7.单链表的按值查找
LNode* LocateElem (LinkList L,Elemtype e)
{
//在带头结点的单链表L中查找值为e的元素
p = L->next; //初始化,p指向首元结点
while (p && p->data!=e) //顺链域向后扫描,直到p为空或p所指结点的数据域等于e
p = p->next;//p指向下一个结点
return p; //查找成功返回值为e的结点地址p,查找失败p为NULL
}
8.单链表的插入
Status ListInsert (LinkList &L,int i,ElemType e)
{
//在带头结点的单链表L中第i个位置插入值为e的新结点
p = L;//L、p指向头结点
j = 0;//计数器,计数到i-1
while (p && (j < i-1))
{
p = p->next;//p往后移动,查找第i-1个结点
++j;//计数器加1
}
if (!p || j>i-1)//i>n+1或i<1
return ERROR;
s = (LinkList) malloc(sizeof(LNode));//创建一个新结点*s
s -> data =e;//新结点的数据域赋值为e
s ->next = p->next;//*s的指针域指向结点ai
p ->next = s;//结点*p的指针域指向结点*s(p指向ai-1)
return OK;
}
9.单链表的删除
Stutas ListDelete (LinkList &L,int i)
{
//在带头结点的单链表L中,删除第i个元素
p = L;
j = 0;
while ((p -> next) && (j < i-1))//查找第i-1个结点,p指向该结点
{
p = p->next;
++j;
}
if (!(p ->next) || (j > i-1))//当 i>n 或 i<1时,删除位置不合理
return ERROR;
q = p->next;//临时保存被删结点的地址以备释放
p->next = q->next;//改变删除结点前驱结点的指针域
free (q);//释放删除结点的空间
return OK;
}
10.前插法创建单链表
void CreateLish_H (LinkList &L,int n)
{
//逆位序输入n个元素的值,建立带头结点的单链表L
L = (LinkList)malloc (sizeof(LNode))
L -> next = NULL;//先建立一个带头结点的空链表
for (int i = 0;i < n;++i)
{
p = (LinkList)malloc(sizeof(LNode));//生成新结点*p
scanf(&p->data);//输入元素值赋给新结点*p的数据域
p->next = L->next;
L->next = p;//将新结点*p插入到头结点之后
}
}
11.后插法创建单链表
void CreateList_R (LinkList &L,int n)
{
//正位序输入n个元素的值,建立带表头结点的单链表L
L = (Linklist)malloc (sizeof(LNode));
L -> next = NULL;//先建立一个带头结点的空链表
r = L;//尾指针r指向头结点
for (i = 0;i < n;++i)
{
p = (LinkList)malloc(sizeof(LNode));//生成新结点
scanf(&p -> data);//输入元素值赋给新结点*p的数据域
p -> next = NULL;//新结点*p的指针域置空
r -> next = p;//将新结点*p插入尾结点*r之后
r = p;
}
}
时间复杂度为O(n)
1.4.2 循环链表
循环链表的概念和特点
循环链表是另一种形式的链式存储结构。
特点:从表中任一结点出发均可找到表中其他结点(也是循环链表的优点)
循环单链表与单链表的差异
循环单链表的操作与单链表基本一致,差别是:判断当前指针p是否指向表尾结点的终止条件不同(在循环链表中 p -> next != L 或 p != L 来判断)。
循环单链表设立尾指针的优点
表的操作常常是在表的首尾位置上进行,所以在循环链表中设立尾指针而不设置头指针可以使一些操作简化:
- 循环单链表中需要找an和a1,尾指针就比较方便:
| 头指针L | 尾指针R | |
|---|---|---|
| 找an的时间复杂度 | O(n) | O(1) ( R -> next -> next ) |
| 找a1的时间复杂度 | O(1) | O(1) ( R ) |
-
将两个线性表(循环单链表)合并成一个表:
假设A是第一个线性表的尾指针;B是第二个线性表的尾指针:
- p = A -> next
- A -> next = B -> next -> next
- free (B -> next)
- B -> next = p
1.4.3 双向链表
双向链表的引入
在单链表中,查找直接后继结点的执行时间为O(1),查找直接前驱的执行时间为O(n)。为了克服这种单向性的缺点,可以利用双向链表。
双向链表的结构
| prior 指针域 | data 数据域 | next 指针域 |
|---|
1.双向链表的插入
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{
//在带头结点的双向链表L中第i个位置插入元素e
if (!(p = GetElem_DuL(L,i)))//在L中确定第i个元素的位置指针p
return ERROR;
s = (DuLinkList)malloc(sizeof(DuLNode));//生成新结点*s
s -> data = e;//将结点*s的数据域置成e
s -> prior = p -> prior;
p -> prior -> next = s;
s -> next = p;
p -> prior = s;
return OK;
}
2.双向链表的删除
Status ListDelete_DuL(DuLinkList &L,int i)
{
//删除带头结点的双向链表L中的第i个元素
if (!(p = GetElem_DuL(L,i)))//在L中确定第i个元素的位置指针p
return ERROR;//p为NULL时,第i个元素不存在
p -> prior -> next = p -> next;
p -> next

最低0.47元/天 解锁文章
2057

被折叠的 条评论
为什么被折叠?



