【数据结构与算法】第三章:表、栈和队列

【数据结构与算法】第三章:表、栈和队列

标签(空格分隔): 【数据结构与算法】


第三章:表、栈和队列

3.1 抽象数据类型

  • 抽象数据类型(abstract data type,ADT)
    ADT是数学的抽象,在ADT的定义中根本没有设计如何实现操作的集合。可以看作是模块化设计的扩充。例如,对于集合ADT,我们有(union),(intersection),测定大小(size),取余(complement),查找(find)等操作.
    思想:这些操作的实现只在程序中编写一次,在程序中任何其他部分要在该ADT上运行其中的一种操作时可以通过调用适当的函数来进行.

3.2 表ADT

形如 A 1 , A 2 , . . . A N 的表,我们说这个表的大小是N.

  • 名词解释
    1. 空表:大小为0的表.(empty list)
    2. 前驱元、后继元:对于非空表以外的任何表,我们说 A i + 1 A i 的后继元,称 A i A i + 1 的前驱元.对于 A 1 没有前驱元,对于 A N 没有后继元.
  • 一些ADT
    1. PrintList:打印链表.
    2. MakeEmpty:创建一个空链表.
    3. Find:返回关键字首次出现的位置.
    4. Insert:从表的某个位置插入某个关键字.
    5. Delete:从表的某个位置删除某个关键字.
    6. FindKth:返回某个位置.
    7. Length:返回链表长度.

3.2.1 表的数组实现

\\制作空表
List MakeEmpty(){
    List PtrL;
    PtrL = ( List)malloc( sizeof( struct LNode));
    PtrL = -1;
    return PtrL;
}
\\查找某个元素 返回下标
int Find( ElementType X, List PtrL){
    int i;
    while( i <= PtrL->Last && PtrL->Data[i] != X)
        i++;
    if( i > PtrL->Last) return -1;
    else return i;
}
\\在第i个位置上插入元素
void Inser( ElementType X, int i, List PtrL){
    int j;

    if( PtrL->Last == MAXSIZE - 1){
        printf("表满");
        return;
    }
    if( i < 1 || i > PtrL->Last + 2){
        printf("位置不合法");
        return;
    }
    for( j = PtrL->Last; j > i; j--)
        PtrL->Data[j+1] = PtrL->Data[j];

    PtrL->Data[i] = X;
    PtrL->Last++;
}
\\删除第i个位置的元素 
void Delete( int i, List PtrL){
    int j;

    if( i < 1 || i > PtrL->Last + 1){
        printf("位置不合法");
        return;
    }

    for( j = i; j < PtrL->Last; j++)
        PtrL->Data[j] = PtrL->Data[j+1];

    PtrL->Last--;
}

3.2.2 链表

  • 链表由一系列不必再内存中相连的结构组成。每个结构均含有表元素和指向包含该元素后继元的结构的指针.这个指针便是Next指针.
  • ANSI C 规定NULL为零.
  • 表头\哑结点:一个标志结点,约定表头处于位置0处.
    image.png-20.8kB
//ADT
List MakeEmpty( List L);
//是否是空表
int IsEmpty( List L);                           
//当前位置是否是末尾
int IsLast( Position P, List L);  
//返回X的位置
Position Find( ElementType X, List L);    
//返回X的前一个位置
Position FindPrevious( ElementType X, List L); 
//删除某个位置元素
void Delete( ElementType X, List L);           
//插入
void Insert( ElementType X, List L, Position P);
void DeleteList( List L);
Position Header( List L);
Position First( List L);
Position Advance( Position P);
ElementType Retriece( Position P);
//具体实现
int IsEmpty( List L){   //Return true if L is empty
    return L->Next == NULL;
}

int IsLast( Position P, List L){
    return P->Next == NULL;
}

Position Find( ElementType X, List L){
    Position P;
    P = L->Next;
    while( P != NULL && P->Element != X)
        P = P->Next;
    return P;
}

Position FindPrevious( ElementType X, List L){
    //假设存在表头
    Position P;
    P = L;
    while( P->Next != NULL && P->Next->Element != X)
        P = P->Next;
    return P;
}

void Delete( ElementType X, List L){
    Position P, TmpCell;
    P = FindPrevious( X, L);        //假设存在表头
    if( !IsLast( P,L)){             //P不是最后一个位置时
        TmpCell = P->Next;          //考虑到P是指向X的钱一个位置
        P->Next = TmpCell->Next;    //若X存在,则P肯定不是最后一个位置
        free( TmpCell);
    }
}

void Insert( ElementType X, List L, Position P){
    Position TmpCell;
    TmpCell = ( Position)malloc( sizeof( struct Node));
    if( TmpCell == NULL)
        FatalError("Out of space!");
    TmpCell->Element = X;
    TmpCell->Next = P->Next;
    P->Next = TmpCell;
}

3.2.3 双链表

在数据结构上附加一个域,使它包含只想钱一个单元的指针.
代价:由于需要更多的指纹定位,因此增加了空间的需求,同时使得删除和插入的开销增加了一倍.

image.png-18.7kB

3.2.4 循环链表

最后的单元反过来直接指向第一个单元.
它可以有表头、也可以没有.当表头存在的时候,便让最后一个单元指向它.
还可以是一种双向链表.
image.png-31.4kB

3.2.5 例题

  • 多项式ADT
    定义一种一元(具有非负次幂)多项式的抽象数据类型,

    F ( X ) = i = 0 N A i X i

    1. 对于大部分系数为0的多项式,可以使用一个简单数组来储存这些系数.之后进行加减乘除等操作.
//数组实现声明
typedef struct{
    int CoeffArray[ NaxDegree + 1];
    int HighPower;
} *Polynomial;

//初始化为0
void ZeroPolynomial( Polynomial Poly){
    int i;
    for( i=0; i <= MaxDegree; i++)
        Poly->CoeffArray[ i] = 0;
    Poly->HighPower = 0;
}

//相加
void AddPolynomial( const Polynomial Poly1, const Polynomial Poly2,
                   Polynomial PolySum){
    int i;
    ZeroPolynomial( PolySum);
    PolySum->HighPower = Poly1->HighPower > Poly2->HighPower ? Poly1->HighPower : Poly2->HighPower;
    for( i = PolySum->HighPower; i >= 0; i--){
        PolySum->CoeffArray[i] = Poly1->CoeffArray[i] + Poly2->CoeffArray[i];
    }
}

//相乘
void MultPolynomial( const Polynomial Poly1, const Polynomial Poly2,
                   Polynomial PolyProd){
    int i, j;
    ZeroPolynomial( PolyProd);
    PolyProd->HighPower = Poly1->HighPower * Poly2->HighPower;
    if( PolyProd->HighPower > MaxDegree)
        Error("Exceeded array size");
    else 
        for( i = 0; i < Poly1->HighPower; i++)
            for( j = 0; j < Poly2->HighPower; j++)
                PolySum->CoeffArray[ i+j] = Poly1->CoeffArray[i] + Poly2->CoeffArray[j];
}

2 . 使用单链表表示,多项式的每一项包含于一个单元中,同时,这些单元以次数递减的顺序排序.如下图:
image.png-48kB

//链表实现声明
typedef struct Node *PtrToNode;

struct Node{
    int Coefficient;
    int ExPonent;
    PtrToNode Next;
};

typedef PtrToNode Polynomial;

void Attach( int c, int e, Polynomial *pRear){
    Polynomial P;
    P = ( Polynomial)malloc( sizeof( struct Node));
    P->Coef = c;
    P->Expon = e;
    *pRear -> Next = P;
    *pRear = P;
}

//相加
Polynomial PolyAdd( Polynomial P1, Polynomial P2){
    Polynomial front, rear, temp;
    int sum;
    rear = ( Polynomial)malloc( sizeof( struct Node));
    front = rear;   //front 指向表头
    while( P1 && P2){
        if( P1->Expon > P2->Expon){
            Attach( P1->Coef, P1->Expon, &rear);
            P1 = P1->Next;
        }
        else if( P1->Expon < P2->Expon){
            Attach( P2->Coef, P2->Expon, &rear);
            P2 = P2->Next;
        }
        else if( P1->Expon < P2->Expon){
            sum = P1->Coef + P2->Coef;
            if( sum)    //sum not equals 0
                Attach( sum, P1->Expon, &rear);
            P2 = P2->Next;
            P1 = P1->Next;
        }
    }
    for( ; P1; P1 = P1->Next) Attach( P1->Coef, P1->Expon, &rear);
    for( ; P2; P2 = P2->Next) Attach( P2->Coef, P2->Expon, &rear);
    rear->Next = NULL;
    temp = front;
    front = front->Next;
    free(temp);
    return fornt;
}

//相乘
Polynomial PolyMult( Polynomial P1, Polynomial P2){
    Polynomial P, Rear, t1, t2, t;
    int c, e;
    if( !P1 || !P2) return NULL; //存在空表时
    t1 = P1; t2 = P2;
    P = ( Polynomial)malloc( sizeof( struct Node));
    Rear = P;
    while( t2){ //先用P1的第一项乘P2
        Attach( t1->Coef * t2->Coef, t1->Expon + t2->Expon, &Rear);
        t2 = t2->Next;
    }
    t1 = t1->Next;
    while( t1){
        t2 = P2; Rear = P;
        while( t2){
            c = t1->Coef * t2->Coef;
            e = t1->Expon + t2->Expon;
            while( Rear->Next && Rear->Next->Expon > e)
                Rear = Rear->Next;
            if( Rear->Next && Rear->Next->Expon == e){
                if( Rear->Next->Coef + c)
                    Rear->Next->Coef += c;
                else{
                    t = Rear->Next;
                    Rear->Next = t->Next;
                    free(t);
                }
            }
            else{
                t = ( Polynomial)malloc( sizeof( struct Node));
                t->Coef = c; t->Expon = e;
                t->Next = Rear->Next;
                Rear->Next = t;
                Rear = Rear->Next;
            }
            t2 = t2->Next;
        }
        t1 = t1->Next;
    }
    t2 = P;
    P = P->Next;
    free( t2);
    return P;
}
  • 基数排序
    基数排序有时候也成为卡式排序.
    桶式排序:假定我们有 N 个整数,大小范围从 1 M (或者从 0 M 1 ),我们创建一个数组,名为 C o u n t ,大小为 M ,并初始化为0,当 A i 被读入时, C o u n t [ A i ] 1 .当所有的输入被读进后,扫描数组 C o u n t ,打印输出排好序的表.该算法时间复杂度为 O ( M + N ) .
    基数排序:上述方法的推广.假设我们有 10 个数字,带下范围从 0 到 999 之间,显然,如果采取桶排序的方法,“桶”便太多了. 于是,我们可以对最低的有效位优先进行排序.
    举例:
    输入: 64, 8, 216, 512, 27, 729, 0, 1, 343, 125.
    第一趟排序:对个位数字
0 1 2 3 4 5 6 7 8 9
0 1 512 343 64 125 216 27 8 729

第二趟排序:对十位数字

0 1 2 3 4 5 6 7 8 9
0 512 125 343 64
1 216 27
8 729

第二趟排序:对百位数字

0 1 2 3 4 5 6 7 8 9
0 125 216 343 512 729
1
8
27
64

3.2.6 多重表

3.2.7 链表的游标实现

3.3 栈ADT

3.3.1 栈模型


  • 被限制插入删除只能在一个位置上进行的表,这个位置是表的末端,叫做栈顶.
  • 基本操作
    进栈 P u s h
    出栈 P o p
    捕获.PNG-22kB
  • LIFO: Last In First Out 后进先出

3.3.2 栈的实现

3.3.2.1 单链表

在表的顶端(不可以是尾端,因为尾端无法实现出栈的操作)实现进栈 P u s h 和 出栈 P o p .

struct Node{
    ElementType Element;
    PtrToNode Next;
};

int IsEmpty( Stack S);       //是否是空栈
Stack CreateStack();         //创建一个栈
void DisposeStack( Stack S); 
void MakeEmpty( Stack S);    //变空
void Push( ElementType X, Stack S);
void Pop( Stack S);
ElementType Top( Stack S);   //返回栈顶
//检测是否为空栈
int IsEmpty( Stack S){
    return S->Next == NULL;
}

//创建一个空栈
Stack CreateStack(){
    Stack S;

    S = ( Stack)malloc( sizeof( struct Node));
    if( S == NULL)
        FatalError(" Out of space");
    S->Next = NULL;
    MakeEmpty( S);
    return S;
}

void MakeEmpty( Stack S){
    if( S == NULL)
        Error(" Must use CreateStack first");
    else{
        while( IsEmpty( S))
            Pop( S);
    }
}

//进栈
void Push( ElementType X, Stack S){
    PtrToNode TmpCell;

    TmpCell = ( Stack)malloc( sizeof( struct Node));
    if( TmpCell == NULL)
        FatalError(" Out of space");
    else{
        TmpCell->Element = X;
        TmpCell->Next = S->Next;    //存在表头
        S->Next = TmpCell;
    }
}

//出栈
void Pop( Stack S){
    PtrToNode FirstCell;
    if( IsEmpty( S))
        Error(" Empty stack");
    else{
        FirstCell = S->Next;
        S->Next = S->Next->Next
        free( FirstCell);
    }
}

//返回栈顶元素
ElementType Top( Stack S){
    if( !IsEmpty( S))   //当S不空时
        return S->Next->Element;
    Error(" Empty stack");
    return 0;           //避免警告
} 

3.3.2.2 数组实现

注意:如果使用数组实现,我们需要提前声明数组大小.

struct StackRecord{
    int Capacity;
    int TopOfStack;
    ElementType *Array;
};

int IsEmpty( Stack S);
int IsFull( Stack S);
Stack CreateStack( int MaxElements);
void DisposeStack( Stack S);
void MakeEmpty( Stack S);
void Push( ElementType X, Stack S);
ElementType Top( Stack S);
void Pop( Stack S);
ElementType TopAndPop( Stack S);
//栈的创建
int CreateStack( int MaxElements){
    Stack S;
    if( MaxElements < MinStackSize)
        Error(" Stack size is too small");
    S = ( Stack)malloc( sizeof( struct StackRecord));
    if( S == NULL)
        FatalError(" Out of space");
    S->Array = malloc( sizeof( struct ElementType));
    if( S->Array == NULL)
        FatalError(" Out of space");
    S->Capacity = MaxElements;
    MakeEmpty(S);
    return S;
}

//创建一个空栈
int MakeEmpty( Stack S){
     S->TopOfStack = EmptyTOS;
}

//检测一个栈是否为空
int IsEmpty( Stack S){
    return S->TopOfStack == EmptyTOS;
}

//释放栈
void DisposeStack( Stack S){
    if( S != NULL){
        free( S->Array);
        free( S);
    }
}

//返回栈顶
ElementType Top( Stack S){
    if( !IsEmpty( S))   //栈非空
        return S->Array[ S->TopOfStack];
    Error(" Empty Stack");
    return 0;           //防止出现警告
}

//进栈
void Push( ElementType X, Stack S){
    if( IsFull( S))
        Error(" Full Stack");
    else
        S->Array[ ++ S->TopOfStack ] = X;
}

//出栈
void Pop( Stack S){
    if( IsEmpty( S))
        Error(" Empty Stack");
    else
        S->TopOfStack--;
}

//给出栈顶元素并弹出
ElementType TopAndPop( Stack S){
    if( !IsEmpty( S))
        return S->Array[ S->TopOfStack --];
    Error(" Empty Stack");
    return 0;           //防止出现警告
}

3.3.3 应用

  • 平衡符号

    1. 描述: 序列 “[()]” 是合法的,”[(])”是非法的.
    2. 解题思路:
      创建一个空栈,读入字符直到文件尾.
      如果字符是一个开放的符号,比如左括号,那么就将他推入堆栈中,
      如果字符是一个封闭的括号,比如有括号,当栈是空时,就报错,否则就将栈的元素弹出.如果弹出的不是对应的开放符号,就报错.在文件尾,如果栈非空就报错.
  • 中缀表达式转换成后缀表达式

    1. 改写成后缀表达式方法:对于一行表达式,从左到右依次读取,如果是操作数就将它输出.遇到操作符 A 就将它压入堆栈 (此时可以视 A 为栈顶元素) ,直到遇到下一个操作符 B ,如果操作符 B 比栈顶操作符 (A) 的优先级低(或者相同),则弹出 A ,反之则压入堆栈.
      遇到括号时,把左括号压入堆栈中,并同时从左括号起视为一个新的堆栈.
    2. 举例:
      对于中缀表达式:a + b * c + ( d * e + f) * g

      步骤 堆栈 输出
      1 a
      2 + a
      3 + ab
      4 +* ab
      5 +* abc
      6 + abc*+
      7 + abc*+
      8 +( abc*+
      9 +( abc*+d
      10 +(* abc*+de
      11 +(+ abc*+de*f
      12 +(+ abc*+de*f
      13 +* abc*+de*f+g
      13 abc*+de*f+g*+
  • 递归调用:
    系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,每当从一个函数退出时,就释放这个存储区,因此当前运行的函数的数据必定储存在栈顶.

3.4 队列ADT

  • 队列:
    插入在一端进行而删除在另一端进行的一种表.

3.4.1 队列模型

  • 基本操作:
    E n q u e u e 入队:在表的末端( r e a r 队尾 )插入一个元素.
    D e q u e u e 入队:删除(或返回)表的开头( f r o n t 队头 )的元素.
    image.png-18kB

3.4.2 队列实现

3.4.2.1 队列的链表实现

只能在链表头部进行删除(出队)操作,尾部进行插入(入队)操作

typedef struct Node *PtrToNode;
struct Node{            //队列中的结点
    ElementType Data;
    PtrToNode Next;
};
typedef struct PtrToNode Position;

struct QNode{
    Position Front, Rear;
    int MaxSize;
};
typedef struct QNode *Queue;
//判断是否为空
int IsEmpty( Queue Q){
    return Q->Front == NULL;
}
//出队
ElementType DeleteQ( Queue Q){
    Position FrontCell;
    ElementType FrontElem;

    if( IsEmpty( Q)){
        printf("队列空");
        return ERROR;
    }
    else{
        FrontCell = Q->Front;
        if( Q->Rear == Q->Front)
            Q->Rear = Q->Front = NULL;
        else
            Q->Front = Q->Front->Next;
        FrontElem = FrontCell->Data;
        return FrontElem;
    }
}
//入队
void Enqueue( ElementType X, Queue Q){  
    Position P;
    P = ( Position)malloc( sizeof( struct Node));
    P->Data = X;
    P->Next = NULL;
    Q->Rear->Next = P;
}

3.4.2.2 队列的数组实现

typedef int Position;
struct QNode{
    ElementType *Data;
    Position Front, Rear;
    int MaxSize;
};
typedef QNode *Queue;
//创建队列
Queue CreatQueue( int MaxSize){
    Queue Q = ( Queue)malloc( sizeof( struct QNode));
    Q->Data = ( ElementType *)malloc( MaxSize * sizeof( ElementTYpe));
    Q->Front = Q->Rear = 0;
    Q->MaxSize = MaxSize;
    return Q;
}
//是否满
int IsFull( Queue Q){
    return (Q->Rear + 1) % Q->MaxSize == Q->Front ;
}
//入队
int AddQ( Queue Q, ElementType X){
    if( IsFull( Q)){
        printf("队列满");
        return ERROR;
    }
    else{
       Q->Rear = (Q->Rear+1) % Q->MaxSize;
       Q->Data[Q->Rear] = X;
       return 1;
    }
}
//是否空
int IsEmpty( Queue Q){
    return Q->Front == Q->Rear;
}
//出队
ElementType DeleteQ( Queue Q){
    if( IsEmpty( Q)){
        printf("队列空");
        return ERROR;
    }
    else{
        Q->Front = (Q->Front+1) % Q->MaxSize;
        return Q->Data[ Q->Front];
    }
}

3.4.2 队列的应用

  • 生活中排队购票;
  • 公司的传呼.

我的微信公众号

猜你喜欢

转载自blog.csdn.net/sd4567855/article/details/80647952
今日推荐