Contents

学习笔记:数据结构与算法(二)

数据结构与算法入门(二)

第三章(续)

静态链表(游标实现法):

让数组的元素都是由两个数据域组成,data和cur。也就是 说,数组的每个下标都对应一个data和一个cur。数据域data,用来存 放数据元素,也就是通常我们要处理的数据;而cur相当于单链表中的 next指针,存放该元素的后继在数组中的下标,我们把cur叫做游标。

1
2
3
4
5
6
7
#define MAXSIZE 1000
typedef struct {
    ElemType data;
    int cur;//游标Cursor,为0表示无指向
}Component,
StaticLinkList[MAXSIZE];
//对于不提供结构struct的程序设计语言,可以使用一对并行数据data和cur来处理

初始化静态链表:

1
2
3
4
5
6
7
8
9
//初始化数组状态
Status InitList(StaticLinkList space){
    int i ;
    for (i = 0 ; i <MAXSIZE-1;i++){
        space[i].cur = i +1;
    }
    space[MAXSIZE -1 ].cur = 0;//最后一个元素的cur存放第一个有数值的元素的下标,相当于单链表中的头结点作用
    return OK;
}
静态链表的插入操作:

为了辨明数组中哪些分量未被使用,解决的办法是将所有未被使用过 的及已被删除的分量用游标链成一个备用的链表,每当进行插入时, 便可以从备用链表上取得第一个结点作为待插入的新结点。

1
2
3
4
5
6
7
8
9
int Malloc_SLL(StaticLinkList space ){
    //返回第一个备用空闲的下标
    int i = space[0].cur;
    if (space[0].cur){
        //因为把第一个空闲的下标返回,所以需要把下一个分量拿来备用
        space[0].cur = space[i].cur;
    }
    return i;
}

具体实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//在第i个元素前插入新的数据元素e
Status ListInsert(StaticLinkList L , int i ,ElemType e){
    int j , k , l;
    //k是最后一个元素的下标
    k = MAXSIZE -1;
    if (i<1 || i > ListLength(L)+1){
        return ERROR;
    }
    j = Malloc_SLL(L);//返回空闲分量的游标
    if (j ){
        L[j].data = e;
        //循环找到第i个元素之前的位置
        for ( l = 1; l <i -1 ; l++){
            k = L[k].cur;//k = 第i-1个元素的游标
        }
        L[j].cur = L[k].cur;//让插入元素的cur指向第i个元素
        L[k].cur = j;//让第i-1个元素的cur指向新元素
        return OK;
    }
    return ERROR;
}
静态链表的删除操作:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Status ListDelete(StaticLinkList L, int i ){
    int j , k ;
    if (i <1 || i >ListLength(L)){
        return ERROR;
    }
    k = MAXSIZE -1;
    for (j = 1;j<=i -1;j++){
        k = L[k].cur;
    }
    j = L[k].cur;
    L[k].cur = L[j].cur;
    Free_SSL(L,j);
    return OK;

}
//将下标为k的空闲结点回收到备用链表
Status Free_SSL(StaticLinkList space,int k ){
    //把备用链表的第一个元素的cur值赋给要删除的分量的cur
    space[k].cur =space[0].cur;
    //要删除的分量下标赋值给备用链表的第一个元素的cur
    space[0].cur = k;
}
获取静态链表的元素个数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//返回静态链表的数据元素的个数
int ListLength(StaticLinkList L){
    int j =0 ;
    int i = L[MAXSIZE -1 ].cur;
    while(i){
        i = L[i].cur;
        j++;
    }
    return j;
}
循环链表:

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单 链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。

(不用头指针,而是使用指向终端结点的尾指针)

合并两个循环链表的操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* 保存A表的头结点,即① */
p = rearA->next;
/*将本是指向B表的第一个结点(不是头结点) */
rearA->next = rearB->next->next;
/* 赋值给reaA->next,即② */
q = rearB->next;
/* 将原A表的头结点赋值给rearB->next,即③ */
rearB->next = p;
/* 释放q */
free(q);
双向链表:

双向链表(double linkedlist):是在单向链表的每个结点中,再设置以一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针 域,一个指向直接后继,另一个指向直接前驱。

代码实现:

1
2
3
4
5
typedef struct DulNode{
    ElemType data;
    struct DulNode *prior;//直接前驱指针
    struct DulNode *next ;//直接后续指针
}DulNode,*DuLinkList;

第四章 栈与队列

4.2.1栈的定义:

栈(stack)是限定仅在表尾进行插入和删除操作的线性表 我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底 (bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出 (LastIn First Out)的线性表,简称LIFO结构。

栈的插入操作:也叫进栈,压栈,入栈(push)
栈的删除操作:也叫出栈,弹栈(pop)
4.3栈的抽象数据类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ADT (stack) Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
    InitStack(*S): //初始化操作,建立一个空栈S。
    DestroyStack(*S): // 若栈存在,则销毁它。
    ClearStack(*S): //将栈清空。
    StackEmpty(S): // 若栈为空,返回true,否则返回false。
    GetTop(S, *e): //若栈存在且非空,用e返回S的栈顶元素。
    Push(*S, e): //若栈S存在,插入新元素e到栈S中并成为栈顶元素。
    Pop(*S, *e): // 删除栈S中栈顶元素,并用e返回其值。
    StackLength(S): // 返回栈S的元素个数。
endADT
4.4栈的顺序存储结构及其实现

结构定义:

1
2
3
4
5
6
7
8
//SElemType 视实际情况而定,这里假设为int
typedef int SElemType ;
const int MAXSIZE = 5;
typedef struct
{
    SElemType data[MAXSIZE];
    int top;//表示栈顶指针
}SqStack;

进栈操作(push):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//push
Status Push (SqStack *S , SElemType e )
{
    //栈满
    if (S->top ==MAXSIZE-1){
        return ERROR;
    }
    //栈顶指针加一
    S->top ++;
    //将新插入的元素赋值到栈顶
    S->data[S->top]=e;
    return OK;
}

出栈操作(pop):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//pop
//若栈不空,则删除S的栈顶元素,用e返回其值, 并返回OK;否则返回ERROR
Status Pop(SqStack *S,SElemType e)
{
    //空栈
    if (S ->top ==-1){
        return ERROR;
    }
    e=S->data[S->top];
    S->top--;
    return OK;
}
4.5两栈共享空间

结构:

1
2
3
4
5
6
7
//两栈共享空间
typedef struct
{
    SElemType data[MAXSIZE];
    int top1; //栈一栈顶指针
    int top2; //栈二栈顶指针
}SqDoubleStack;

插入操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Status Push(SqDoubleStack *S,SElemType e,int StackNumber)
{
    //栈满了
    if (S->top1 +1 == S->top2)
    {
        return ERROR;
    }
    if (StackNumber == 1)
        S->data[++S->top1]=e;
    else if (StackNumber ==2)
        S->data[--S->top2]=e;
    return OK;
}

删除操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//如果栈不为空,就用e返回删除的值,并且返回ok,失败则返回error
Status Pop(SqDoubleStack *S,SElemType *e,int StackNumber )
{
    if (StackNumber == 1){
        if (S->top1 ==-1)
        {
            //说明栈一为空
            return ERROR;
        }
        *e = S->data[S->top1--];

    }
    else if (StackNumber ==2){
        if (S->top2 ==-1)
        {
            //说明栈二为空
            return ERROR;
        }
        *e = S->data[S->top2++];
    }
    return OK;
}
4.6 栈的链式存储结构及实现

栈的链式存储结构,简称链栈

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//链栈的结构定义
//链栈结点
typedef struct StackNode
{
    SElemType data;
    struct StackNode *next;
}StackNode,*LinkStackPtr;
//整个链栈
typedef struct LinkStack
{
    LinkStackPtr top;//栈顶指针
    int count ; //栈元素个数
}LinkStack;

进栈操作push:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//进栈操作
//插入e为新元素到链栈的栈顶
Status Push(LinkStack *S,SElemType e)
{

    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    s->data = e;
    //接着将当前栈顶元素赋值给新结点的直接后续,
    s->next = S->top;
    //将新结点赋值给栈顶指针top
    S->top = s;
    S->count ++;
    return OK;
}

出栈操作Pop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//出栈操作
//若栈不为空,就删除栈顶元素并用e来返回删除的元素,
//假设变量p用来存储要删除的栈顶元素,将栈顶指针后移一位,再释放掉p
Status Pop(LinkStack *S,SElemType *e)
{
    LinkStackPtr p ;
    //判断栈是否为空
    if (StackEmpty(S)){
        return ERROR;
    }
    *e= S->top->data;
    //将栈顶结点指针赋值给p
    p = S->top;
    //使栈顶指针下移到后一位
    S->top = S->top->next;
    //释放结点p
    free(p);
    S->count--;
    return OK;
}
4.7栈的应用:
1.递归

递归函数:直接调用自己或通过一系列的调用语句间接地调用自己的函数。 递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。 因此要实现这样的需求,编译器就是使用栈来实现递归

2.四则运算表达式

1.后缀(逆波兰)表达式:所有的符号都要在运算数字的后面出现 2.后缀表达式运算结果:规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行算,运算结果进栈,一直到最终获得结果 3.中缀表达式:标准的四则运算表达式 4.中缀表达式转换为后缀表达式的规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

4.8队列的定义:

队列是只允许在一端进行插入操作,一端进行删除操作的线性表 (First in First out )简称FIFO,允许插入的一头叫做队尾,允许删除的一头叫做队头

4.9队列的抽象数据类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ADT 队列(Queue) Data
//同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitQueue(*Q);//初始化操作,建立一个空队列Q。 
DestroyQueue(*Q);//若队列Q存在,则销毁它。
ClearQueue(*Q);//将队列Q清空。
QueueEmpty(Q);// 若队列Q为空,返回true,否则返回false。
GetHead(Q, *e);//若队列Q存在且非空,用e返回队列Q的队头元素。
EnQueue(*Q, e);// 若队列Q存在,插入新元素e到队列Q中并成为队尾元素
DeQueue(*Q, *e);//删除队列Q中队头元素,并用e返回其值
QueueLength(Q);//返回队列Q的元素个数
endADT
4.10循环队列

循序存储结构的不足: 入队列操作,就是在队尾添加一个元素,时间复杂度为O(1) 出队列操作,就是从线性表下标为0的位置出列,全部元素都要移动,时间复杂度为O(n) 当只有一个元素时,队尾和对头重叠,使得操作麻烦,所以引入两个指针,一个是front指向队头元素,一个是rear指向队尾元素 的下一个位置,这样当front和rear相等时,不是只剩一个元素,而是这是一个空队列 如果一个队列即在出列也在入列,使rear超出了线性表的最大元素个数,但是队列队头还有空闲,这样的现象称为“假溢出”

循环队列定义:队头队尾相连的顺序存储结构

循环队列满的条件:(rear+1)%QueueSize==front 循环队列空的条件:rear = front 循环队列队列长度:(rear-front+QueueSize)%QueueSize 循环队列的结构定义:

1
2
3
4
5
6
7
8
9
//QElemType的类型视实际情况而定,这里假设为int
typedef int QElemType ;

typedef struct 
{
    QElemType data[MAXSIZE];
    int front;
    int rear;//尾追针,指向队列的队尾元素的下一个位置
}SqQueue;

循环列表初始化操作:

1
2
3
4
5
Status InitSqQueue(SqQueue *Q){
    Q->front=0;
    Q->rear=0;
    return OK;
}

获取循环列表的长度:

1
2
3
int SqQueueLenth(SqQueue Q){
    return (Q.front-Q.rear+MAXSIZE)%MAXSIZE;
}

入队操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//插入e到循环队列Q中
Status EnQueue(SqQueue *Q,QElemType e ){
    //先判断是否队列满
    if ((Q->rear +1 +MAXSIZE )%MAXSIZE == Q->front){
        return ERROR;
    }
    //将e赋值给队尾
    Q->data[Q->rear]=e;
    //rear后移一位,若到最后就移动到数组前面
    Q->rear = (Q->rear+1)%MAXSIZE;
    return OK;
}

出队操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//删除对头元素,并用e表示该元素
Status DeQueue(SqQueue *Q,QElemType *e ){
    //判断是否队列空
    if (Q->front == Q->rear){
        return ERROR;
    }
    //将对头赋值给e
    *e = Q->data[Q->front];
    //将front后移一位,若到最后就移动到数组前面
    Q->front = (Q->front +1) %MAXSIZE;
    return OK;
}
4.11链队列

队列的链式存储结构其实就是线性表的单链表,队头指针指向头结点,队尾指针指向终端结点 空队列时,front和rear都指向头结点 链队列的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//QElemType的类型视实际情况而定,这里假设为int
typedef int QElemType ;
//结点定义
typedef struct QNode
{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;
//链队列定义
typedef struct 
{
    //队头队尾指针
    QueuePtr front,rear;
}LinkQueue;

链队列的入队操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//插入元素e到链队列的队尾
Status EnQueue(LinkQueue *Q,QElemType e){
    QueuePtr s  = (QueuePtr)malloc(sizeof(QNode));
    //分配失败
    if (!s ){
        exit(OVERFLOW);
    }
    //将e赋值给s
    s->data= e;
    s->next =NULL;
    //将拥有e的新结点s赋值给原队列后续
    Q->rear->next = s;
    //把当天s设置为队尾结点,rear指向s
    Q->rear = s;
    return OK;
}

链队列的出队操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//若队列不为空,删除队头元素,并用e返回值
Status DeQueue(LinkQueue *Q,QElemType *e){
    QueuePtr p ;
    //判断队列是否为空
    if (Q->front == Q->rear ){
        return ERROR;
    }
    //将队头元素暂存在p中
    p = Q->front->next;
    //将要删除的元素的值赋值给e
    *e = p->data;
    //将原头结点后续的后续赋值给头结点的后续
    Q->front->next = p->next;
    //如果队头是队尾,就将队尾指针指向头结点
    if (Q->rear == p ){
        Q->rear = Q->front;
    }
    free(p);
    return OK;
}
4.12 总结

栈:只允许在表尾进行插入和删除操作的线性表 队列:只允许在队尾插入元素,队头删除元素的线性表

第五章 串

5.1串的定义:

串(string):是指由一个或多个字符组成的有限序列 零个字符的串就是空串 子串就是该串中连续的元素组成的子序列,包含子串的串就是主串 子串的第一个字符在主串的序号就是子串在主串的位置

5.2串的抽象数据类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ADT (string) Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系。
Operation
StrAssign(T, *chars):
StrCopy(T, S):
ClearString(S):
StringEmpty(S): 
StrLength(S):
StrCompare(S, T):
Concat(T, S1, S2):
SubString(Sub, S, pos, len)
Index(S,T,pos):
Replace(S,T,V):
StrInsert(S,pos,T):
StrDelete(S,pos,len):
endADT
5.3串的存储结构

1.串的顺序存储结构:由一组内存地址连续的存储单元进行存储串中的字符序列,一般是根据预定义的长度,为串分配一个定长存储区,一般为定长数组 2.串的链式存储结构:一个结点放一个字符或者多个字符

5.4朴素的模式匹配算法

算法实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//index
//假设S[0],和T[0]存储各字符串的长度
//返回T在S的第pos个字符之后的位置
int Index(string S,string T,int pos){
    //i用于S中当前位置下标,若pos不为1,则从pos位置开始
    int i  = pos;
    //j用于T中当前位置下标
    int j = 1;
    while(i <S[0] &&j <T[0])
    {
        //两字母相同则继续
        if (S[i] == T[j]){
            i ++;
            j++;
        }else {
            //不同就指针回退重新开始匹配
            /* i退回到上次匹配首位的下一位 */ 
            i = i - j + 2;
            /* j退回到子串T的首位 */
            j = 1;
        }
    }
    if (j == T[0]){
        return i -T[0];
    }else {
        return 0;
    }
}
5.5 KMP模式匹配算法

一脸懵逼😳,单独写个博客补充吧😭