mirror of
https://github.com/Didnelpsun/CS408.git
synced 2026-06-17 07:27:12 +08:00
更新数据结构
This commit is contained in:
@@ -33,3 +33,33 @@ $C.$Ⅰ、Ⅱ
|
||||
$D.$Ⅱ、Ⅲ
|
||||
|
||||
解:$C$。对于Ⅱ,顺序表仅需$3$次交换操作;链表则需要分别找到两个结点前驱,第$4$个结点断链后再插入到第$2$个结点后,效率较低。对于Ⅲ,需依次顺序访问每个元素,时间复杂度相同。
|
||||
|
||||
## 链表
|
||||
|
||||
### 结构选择
|
||||
|
||||
**例题** 一个链表最常用的操作是在末尾插入结点和删除结点,则选用()最节省时间。
|
||||
|
||||
$A.$带头结点的双循环链表
|
||||
|
||||
$B.$单循环链表
|
||||
|
||||
$C.$带尾指针的单循环链表
|
||||
|
||||
$D.$单链表
|
||||
|
||||
解:$A$。首先分析要求。末尾删除和插入结点,则需要对尾部操作。$B$是循环链表,但是方向是头结点到尾部,如果操作尾部需要$O(n)$的时间。$C$带了尾指针,所以可以对尾指针直接插入元素,但是不能删除尾指针所指向的最后一个元素,因为是单链表,所以不能找到尾指针的前驱。$D$单链表只能从头开始找,所以不合适。对于$A$。虽然它是带头结点,但是是循环链表,所以头节点的前驱就是尾结点,且是双链表,所以可以向前。若是删除尾结点直接删除头节点的前驱,同理插入尾节点,直接对头节点的前驱操作。
|
||||
|
||||
### 链表逻辑
|
||||
|
||||
**例题** 某线性表用带头结点的循环单链表存储,头指针为`head`,当 `head->next->next=head`成立时,线性表长度可能是()。
|
||||
|
||||
$A.0$
|
||||
|
||||
$B.1$
|
||||
|
||||
$C.2$
|
||||
|
||||
$D.$可能为$0$或$1$
|
||||
|
||||
解:$D$。这个题目很容易就会选择$B$。对于$B$,若是有一个元素,则$head->next$就指向该元素,该元素后面没有了,就指向头节点从而`head->next->next=head`。若元素有$0$个,则`head->next=head`,则`head->next->next=head->next=head`,所以也成立。
|
||||
|
||||
@@ -150,15 +150,15 @@ typedef struct LinkListNode {
|
||||
+ 前插入。
|
||||
+ 后插入。
|
||||
|
||||
假定从第一个结点开始就是第0索引的结点。
|
||||
假定从第一个结点开始就是第$0$索引的结点。
|
||||
|
||||
带头结点的单链表头结点就是0号结点,不带头节点的第一个数据结点就是0号结点。
|
||||
带头结点的单链表头结点就是$0$号结点,不带头节点的第一个数据结点就是$0$号结点。
|
||||
|
||||
带头结点的单链表只能往头结点之后插入,所以插入索引必须从1开始。
|
||||
带头结点的单链表只能往头结点之后插入,所以插入索引必须从$1$开始。
|
||||
|
||||
头插法建立单链表:
|
||||
|
||||
+ 每个结点的插入时间为$O(1)$,设单链表长为n,则总时间复杂度为$O(n)$。
|
||||
+ 每个结点的插入时间为$O(1)$,设单链表长为$n$,则总时间复杂度为$O(n)$。
|
||||
+ 实现了输入数据的就地逆置。
|
||||
|
||||
尾插法建立单链表
|
||||
@@ -191,7 +191,7 @@ typedef struct LinkListNode {
|
||||
|
||||
按位查找时间复杂度为$O(n)$。
|
||||
|
||||
这样插入元素函数InsertLinkListWithHead只用`GetLinkListNode(list,i-1)`和`InsertNextLinkNode(p,elem)`两个函数完成。
|
||||
这样插入元素函数`InsertLinkListWithHead`只用`GetLinkListNode(list,i-1)`和`InsertNextLinkNode(p,elem)`两个函数完成。
|
||||
|
||||
#### 单链表建立
|
||||
|
||||
@@ -203,7 +203,7 @@ typedef struct LinkListNode {
|
||||
|
||||
## 双链表
|
||||
|
||||
为了解决单链表只能单一方向扫描而无法两项遍历的缺点,使用了两个指针,prior和next,分别指向前驱和后继。
|
||||
为了解决单链表只能单一方向扫描而无法两项遍历的缺点,使用了两个指针,`prior`和`next`,分别指向前驱和后继。
|
||||
|
||||
### 双链表定义
|
||||
|
||||
@@ -211,31 +211,54 @@ typedef struct LinkListNode {
|
||||
|
||||
### 双链表操作
|
||||
|
||||
#### 双链表初始化
|
||||
<!-- #### 双链表初始化 -->
|
||||
|
||||
#### 双链表插入
|
||||
|
||||
假如`p`的结点后要插入结点`s`,则基本代码如下:
|
||||
|
||||
```c
|
||||
// 将插入的结点的后续接上原来的p的后续
|
||||
s->next=p->next
|
||||
// 将p后的结点的前驱连接到s上
|
||||
p->next->prior=s;
|
||||
// 将s的前驱连接到p上
|
||||
s->prior=p;
|
||||
// 将p的后继连接到s上
|
||||
p->next=s;
|
||||
```
|
||||
|
||||
操作上都是成对的,其中第一条第二条指令必须在最后一条指令之前,否则`p`的后继就会丢掉。
|
||||
|
||||
#### 双链表删除
|
||||
|
||||
若删除结点`p`的后继结点`q`:
|
||||
|
||||
```c
|
||||
p->next=q->next;
|
||||
q->next->prior=p;
|
||||
free(q);
|
||||
```
|
||||
|
||||
## 循环链表
|
||||
|
||||
分为循环单链表和循环双链表。基本上变化不大。
|
||||
|
||||
原来的单链表的尾部指向NULL,但是循环单链表的尾部是指向头部。
|
||||
原来的单链表的尾部指向`NULL`,但是循环单链表的尾部是指向头部。
|
||||
|
||||
循环单链表即使没有头结点的地址,也可以通过循环得到整个单链表的信息。
|
||||
|
||||
从头到尾单链表需要遍历整个链表,而循环单链表只用移动一位就可以从头到尾。
|
||||
|
||||
循环双链表除此之外,头结点的prior指针还要指表尾结点(即某结点*p为尾结点时,p->next==list)。
|
||||
循环双链表除此之外,头结点的`prior`指针还要指表尾结点(即某结点`*p`为尾结点时,`p->next==list`)。
|
||||
|
||||
循环双链表为空表时,头结点的prior和next域都等于list(即,指向自身)。
|
||||
循环双链表为空表时,头结点的`prior`和`next`域都等于`list`(即,指向自身)。
|
||||
|
||||
### 循环链表定义
|
||||
|
||||
循环链表和链表的结点定义是一致的。
|
||||
|
||||
### 循环链表操作
|
||||
<!-- ### 循环链表操作
|
||||
|
||||
#### 循环单链表初始化
|
||||
|
||||
@@ -243,7 +266,7 @@ typedef struct LinkListNode {
|
||||
|
||||
#### 循环双链表插入
|
||||
|
||||
#### 循环双链表删除
|
||||
#### 循环双链表删除 -->
|
||||
|
||||
## 静态链表
|
||||
|
||||
@@ -253,9 +276,11 @@ typedef struct LinkListNode {
|
||||
|
||||
静态链表和顺序表一样需要预先分配一块连续的内存空间。
|
||||
|
||||
数组0号元素充当链表的头结点且不包含数据。
|
||||
数组$0$号元素充当链表的头结点且不包含数据。
|
||||
|
||||
如果一个结点是尾结点,其游标设置为-1。
|
||||
如果一个结点是尾结点,其游标设置为$-1$。
|
||||
|
||||
具体的实现方式有多种,也可以$0$号元素数据保存头节点下标。
|
||||
|
||||
考的比较少。
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 栈
|
||||
|
||||
栈结构与线性表类似,是只允许一端(表尾)进入或删除的线性表。即后进先出LIFO。
|
||||
栈结构与线性表类似,是只允许一端(表尾)进入或删除的线性表。即后进先出$LIFO$。
|
||||
|
||||
栈顶就是允许插入和删除的一端,而另一端就是栈底。
|
||||
|
||||
进栈顺序:A->B->C->D,出栈顺序:D->C->B->A。
|
||||
进栈顺序:$A\rightarrow B\rightarrow C\rightarrow D$,出栈顺序:$D\rightarrow C\rightarrow B\rightarrow A$。
|
||||
|
||||
如果有$n$个不同的元素进栈,出栈元素不同排列的个数为$\dfrac{1}{n+1}C_{2n}^n$,这就是卡特兰数。
|
||||
|
||||
@@ -12,15 +12,33 @@
|
||||
|
||||
### 顺序栈定义
|
||||
|
||||
设置栈顶指针可以为$0$(代表栈顶元素的下一个存储单元)也可以为$-1$(代表栈顶元素当前未知),
|
||||
|
||||
### 顺序栈操作
|
||||
|
||||
#### 顺序栈初始化
|
||||
|
||||
栈顶指针初始化为-1,因为索引最小为0。如果初始化为0也可以,不过其操作有所不同。
|
||||
栈顶指针初始化为$-1$,因为索引最小为$0$。如果初始化为$0$也可以,不过其操作有所不同。
|
||||
|
||||
#### 进栈
|
||||
|
||||
首先要判满,然后才能进栈。若栈顶指针指的是当前元素,即初值为$-1$,需要先自加再进栈,如果不先自加就会覆盖在原来的栈顶元素上。若栈顶指针指的是当前元素的下一个位置,即初值为$0$,则先进栈再自加,因为指向的下一个位置,所以指向的位置是空的,所以可以存入然后自加,若先自加则中间就空了一格。
|
||||
|
||||
#### 出栈
|
||||
|
||||
首先要判空,然后才能出栈。若栈顶指针指的是当前元素,即初值为$-1$,需要先出栈再自减,如果由于指的是当前元素所以要先将这个指向的元素弹出,然后自减,否则弹出的就是靠近栈底的下一个元素。若栈顶指针指的是当前元素的下一个位置,即初值为$0$,则先自减再出栈,因为指向的下一个位置,所以指向的位置是空的,要先自减指向有元素的一格才能出栈。
|
||||
|
||||
对于出栈元素的处理,既可以将原来的存储单元设置为`NULL`也可以不处理,因为栈顶指针不指向这些单元,用户是不知道里面是什么的,之后重新用到这些存储单元也会覆盖原来的数据。
|
||||
|
||||
### 共享栈
|
||||
|
||||
即根据栈底不变,让两个顺序栈共享一个一维数组,将两个栈的栈底设在数组两端,栈顶向共享空间延申。
|
||||
|
||||
存取数据时间复杂度为$O(1)$。
|
||||
|
||||
## 链栈
|
||||
|
||||
链栈基本上就是只能操作一头的链表,所以从定义上其基本上没有区别。
|
||||
链栈基本上就是只能操作一头的链表,所以从定义上其基本上没有区别。基本上以表头为栈顶。
|
||||
|
||||
## 栈的应用
|
||||
|
||||
|
||||
Reference in New Issue
Block a user