本文记录王道数据结构各章知识点部分的代码
Ch2
2.1 线性表的定义和基本操作
2.1.2 线性表的基本操作
InitList(&L)
:初始化表。构造一个空的线性表。Length(L)
:求表长。返回线性表L的长度,即L中数据元素的个数。LocateElem(L,e)
:按值查找操作。在表L中查找具有给定关键字值的元素。GetElem(L,i)
:按位查找操作。获取表L中第i个位置的元素的值。ListInsert(&L,i,e)
:插入操作。在表L中的第i个位置上插入指定元素e。ListDelete(&L,i,&e)
:删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。PrintList(L)
:输出操作。按前后顺序输出线性表L的所有元素值。Empty(L)
:判空操作。若L为空表,则返回true,否则返回false。DestroyList(&L)
:销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
“&”表示C++语言中的引用调用,在C语言中采用指针也可达到同样的效果。
2.2 线性表的顺序表示
2.2.1 顺序表的定义
假定线性表的元素类型为ElemType,则线性表的顺序存储类型描述为:
a.静态分配
1 | #define MaxSize 50 //定义线性表的最大长度 |
b.动态分配
1 | #define InitSize 100 //表长度的初始定义 |
C的初始动态分配语句为:L.data = (ElemType *)malloc(sizeof(ElemType)*InitSize);
C++的初始动态分配语句为:L.data = new ElemType[InitSize];
2.2.2 顺序表上基本操作的实现
(1)插入操作
在顺序表L的第i(1<=i<=L.length+1)个位置插入新元素e。平均时间复杂度为O(n)。
1 | bool ListInsert(SqList &L, int i, ElemType e) { |
(2)删除操作
删除顺序表L中第i(1<=i<=L.length)个位置的元素,用引用变量e返回。平均时间复杂度为O(n)。
1 | bool ListDelete(SqList &L, int i, ElemType &e) { |
(3)按值查找(顺序查找)
在顺序表L中查找第一个元素值等于e的元素,并返回其位序。平均时间复杂度为O(n)。
1 | int LocateElem(SqList L, ElemType e) { |
2.3 线性表的链式表示
2.3.1 单链表的定义
单链表中结点类型的描述如下:
1 | typedef struct LNode{ //定义单链表结点类型 |
2.3.2 单链表上基本操作的实现
1.采用头插法建立单链表
该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。
算法如下:
1 | LinkList List_HeadInsert(LinkList &L) { //逆向建立单链表 |
每个结点插入的时间是O(1),设单链表长为n,则总时间复杂度为O(n)。
2.采用尾插法建立单链表
该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点。
算法如下:
1 | LinkList List_TailInsert(LinkList &L) { //正向建立单链表 |
因为附设了一个指向表尾结点的指针,故时间复杂度和头插法的相同。
3.按序号查找结点值
在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。
算法如下:
1 | LNode *GetElem(LinkList L, int i) { |
时间复杂度为O(n)。
4.按值查找表结点
从单链表的第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。
算法如下:
1 | LNode *LocateElem(LinkList L, ElemType e) { |
时间复杂度为O(n)。
5.插入结点操作
插入结点操作将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,再在其后插入新结点。
代码片段如下:
1 | p = GetElem(L, i-1); //查找插入位置的前驱结点 |
本算法主要的时间开销在于查找第i-1个元素,时间复杂度为O(n)。若在给定的结点后面插入新结点,则时间复杂度仅为O(1)。
可采用另一种方式将前插操作转化为后插操作来实现,设待插入结点为*s
,将*s
插入到*p
的前面。我们仍然将*s
插入到*p
的后面,然后将p->data
与s->data
交换,这样既满足了逻辑关系,又能使得时间复杂度为O(1)。算法的代码片段如下:
1 | //将*s结点插入到*p之前的主要代码片段 |
6.删除结点操作
删除结点操作是将单链表的第i个结点删除。先检查删除位置的合法性,后查找表中第i-1个结点,即被删结点的前驱结点,再将其删除。
代码片段如下:
1 | p = GetElem(L, i-1); //查找删除位置的前驱结点 |
和插入算法一样,该算法的主要时间夜耗费在查找操作上,时间复杂度为O(n)。
其实,删除结点*p
的操作可用删除*p
的后继结点操作来实现,实质就是将其后继结点的值赋予其自身,然后删除后继结点,也能使得时间复杂度为O(1)。
实现上述操作的代码片段如下:
1 | q = p->next; //令q指向*p的后继结点 |
7.求表长操作
求表长操作就是计算单链表中数据结点(不含头结点)的个数,需要从第一个结点开始顺序依次访问表中的每个结点,为此需要设置一个计数器变量,每访问一个结点,计数器加1,知道访问到空结点为止。算法的时间复杂度为O(n)。
2.3.3 双链表
双链表中结点类型的描述如下:
1 | typedef struct DNode{ //定义双链表结点类型 |
插入、删除操作的时间复杂度仅为O(1)。
插入操作的代码片段如下:
1 | s->next = p->next; //将结点*s插入到结点*p之后 |
删除操作的代码片段如下:
1 | p->next = q->next; //删除*p后的*q |
2.3.5 静态链表
静态链表结构类型的描述如下:
1 | #define MaxSize 50 //静态链表的最大长度 |