嵌入式團隊培訓_線性結構

一、線性表

定義:零個或多個數據元素的有限序列
要求:第一個元素無前驅,最後一個元素無後繼,其他元素都有且只有一個前驅和後繼

思考:循環鏈表?雙向鏈表?

線性表的抽象數據類型ADT(書p45),其代碼表示:

在list.h頭文件中
#include <stdio.h>

//定義線性表長度大小
#define MAXSIZE 100

//增強代碼可讀性
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;


/**
 * 線性表的聲明
 */
typedef struct list {
    //數據元素
    ElemType data[MAXSIZE];
    //線性表的長度
    int length;
}SqList;

//線性表CRUD功能聲明
void initList(SqList* L); /*1.初始化一個空的線性表L*/
Status isEmpty(SqList L); /*2.判斷線性表是否爲空,空返回true*/
void clearList(SqList* L); /*3.清空線性表*/
int listLength(SqList L); /*4.返回線性表L的長度即元素個數*/
void getElem(SqList L,int i,ElemType* e); /*5.將線性表L中第i個位置元素值返回給e*/
int locateElem(SqList L,ElemType* e); /*6.將線性表L中元素值爲e的,查找成功返回第i,否則返回0*/
Status listInsert(SqList* L,int i,ElemType e); /*7.在線性表L中第i個位置插入元素e,成功返回true*/
Status listDelete(SqList* L,int i,ElemType* e); /*8.在線性表L刪除第i個位置的元素,並將其值返回給e,成功返回true*/

1、順序存儲

定義:用一段連續的地址存儲單元依次存儲線性表的數據元素。
要求:數據元素類型相同。
所以我們可以用一維數組來實現順序存儲結構。
在這裏插入圖片描述
第i個數據元素ai的存儲位置:LOC(ai) = LOC(a1)+(i - 1)* c (c爲每個數據元素佔據的存儲單元)

#define MAXSIZE 100 /*初始化空間分類量(內存分配)*/

//增強代碼可讀性
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;


/**
 * 線性表的聲明
 */
typedef struct list {
    //數據元素
    ElemType data[MAXSIZE];
    //線性表的長度
    int length;
}SqList;

由於查找,修改操作過於簡單,我們主要講插入刪除操作

首先分清這三個概念
下標:從0開始,例如a0,a1
第i個:從1開始,例如第一個元素(a0),第n個元素(an-1)
線性表長度L->length:數據元素的個數 n

/**
 * 插入條件:線性表L已經存在,且1 <= i <= listLength(L)
 * 操作:在線性表L中第i個位置之前插入元素e
 * @param L 線性表L
 * @param i 位置座標
 * @param e 插入元素
 * @return 狀態值
 */
Status listInsert(SqList* L,int i,ElemType e) {

    //1.算法的健壯性
    if (L->length == MAXSIZE) {/*線性表已經滿了*/
        return ERROR;
    }
    /*或者if (listLength(*L) == MAXSIZE) {
        return ERROR;
    }*/
    
    if (i < 1 || i > L->length + 1) {/*i不在插入的範圍內*/
        return ERROR;
    }
    
    if (i <= L->length) { /*此時i滿足條件,並且不是在表尾進行插入*/
        //將第i個位置後面的元素,依次後移纔有位置進行插入
        for (int k = L->length - 1; k >= i - 1; k++) {
            L->data[k + 1] = L->data[k];
        } 
    }    
    L->data[i - 1] = e; /*將新元素插入*/
    L->length++; /*長度加1*/
    return OK;
}

/**
 * 刪除條件:線性表L已經存在,且1 <= i <= listLength(L)
 * 在線性表L刪除第i個位置的元素,並將其值返回給e
 * @param L 線性表L
 * @param i 位置座標
 * @param e 刪除位置的元素值
 * @return 狀態值
 */
Status listDelete(SqList* L,int i,ElemType* e) {
    
     //1.算法的健壯性
    if (L->length == 0) {/*線性表已經空了*/
        return ERROR;
    }
    /**或者 if (isEmpty(*L)) {
        return ERROR;
    }*/
    
    if (i < 1 || i > L->length) {/*i不在刪除的範圍內*/
        return ERROR;
    }
    
    *e = L->data[i - 1]; /*取刪除位置的值賦給e*/
    
    if(i < L->length) { /*如果刪除位置不是在表尾*/
        //將刪除位置後的後繼元素前移
        for (int k = 0; k < L->length; ++k) {
            L->data[k - 1] = L->data[k];
        }
    }
    
    L->length--; /*長度減1*/
    return OK;
}

2、鏈式存儲

單鏈表的創建

/**
 * 創建一個單鏈表
 * @param L 單鏈表頭指針
 * @param n 單鏈表元素個數
 */
void createList(LinkList* L,int n) {
    //定義兩個指針,分別是用於申請內存的臨時指針,一個是最後用於指向尾結點的指針
    LinkList* p, r; 
    
    L = (LinkList*)malloc(sizeof(Node));
    
    r = L;
    for (int i = 0; i < n; i++) {
        p = (LinkList*)malloc(sizeof(Node));
        r->next = p;
        r = p; /*循環創建動起來*/
    }
    r->next = NULL;
}

CRUD操作參照單鏈表講義鏈表講義,這裏不再贅述。

頭指針與頭結點區別

頭結點:(可有可無)放在第一個結點前,一般存放鏈表長度等,有了頭結點對於刪除第一個元素就跟其他元素操作統一了。
頭指針:指向第一個結點,如果有頭結點就指向頭結點。

在這裏插入圖片描述

特殊鏈表結構

1)靜態鏈表(瞭解即可):用數組描述的鏈表,簡單說就是結構體數組,結構體有個遊標(cur)指向下一個結構體的下標。
(2)循環鏈表

在這裏插入圖片描述
(3)雙向鏈表
在這裏插入圖片描述
注意:不要忘記頭結點的前驅指向最後一個結點。

/**
 * 雙向鏈表的聲明
 */
typedef struct dulNode {
    //數據元素
    ElemType data;
    
    struct dulNode* prior; /*直接前驅結點*/ 
    struct dulNode* next;  /*直接後繼結點*/
}dulNode;

插入、刪除操作特別注意代碼的書寫順序

雙向鏈表的插入
在這裏插入圖片描述
雙向鏈表的刪除
在這裏插入圖片描述

3、順序存儲與鏈式存儲優缺點

對於我們如何選擇順序存儲或者是鏈式存儲,我們先來比較他們的優缺點

(1)插入:要在線性表中插入元素,顯然順序存儲時間複雜度O(n),而鏈式存儲時間複雜度O(1)
(2)刪除:要在線性表中刪除元素,顯然順序存儲時間複雜度O(n),而鏈式存儲時間複雜度O(1)
(3)查找:要在線性表中查找元素,顯然順序存儲時間複雜度O(1),而鏈式存儲時間複雜度O(n)
(4)修改:要在線性表中修改元素,顯然順序存儲時間複雜度O(1),而鏈式存儲時間複雜度O(n)

結論:
(1)線性表需要頻繁的查找,建議順序存儲。如果需要頻繁的插入刪除,則選擇鏈式存儲爲宜(例如學生管理系統)。
(2)元素個數較大或者根本不知道有多少個,用鏈式存儲爲宜。但是如果事先知道大致長度,且長度不是特別大(例如:一年12個月)順序存儲效率高,不需要頻繁申請內存。

二、棧

定義:限定僅在表尾進行操作的線性表。(就是個只能在一端操作,且有棧頂指針的數組)
允許操作的一端叫做棧頂(top),另一端叫做棧底(base),棧是遵循先進後出原則設計的線性表。

例如:網頁的返回鍵,數制轉換,遞歸函數。

棧的抽象數據類型ADT(書p91),其代碼表示:

//在stack.h頭文件中
#include <stdio.h>

//定義棧長度大小
#define MAXSIZE 100

//增強代碼可讀性
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int SElemType;
typedef int Status;


/**
 * 棧的聲明
 */
typedef struct Stack {
    //數據元素
    SElemType data[MAXSIZE];
    //棧頂位置
    int top;
}SqStack;

//棧的CRUD功能聲明
void initStack(SqStack* S); /*1.初始化一個空棧S*/
Status isEmpty(SqStack L); /*2.判斷棧是否爲空,空返回true*/
void clearStack(SqStack* L); /*3.清空棧*/
void destroyStack(SqStack* S); /*4.若棧存在,則銷燬它*/
void getTop(SqStack L,SElemType* e); /*5.若棧存在非空,就用e返回S的棧頂元素*/
int stackLength(SqStack L); /*6.返回棧的長度即元素個數*/
Status push(SqStack* S,SElemType e); /*7.若棧存在,插入新元素e到S中作爲棧頂元素*/
Status pop(SqStack* S, SElemType* e); /*8.刪除S中的棧頂元素,用e來接收*/

1、棧的順序存儲

/**
 * 棧的聲明
 */
typedef struct Stack {
    //數據元素
    SElemType data[MAXSIZE];
    //棧頂位置
    int top;
}SqStack;

注意:
(1)定義一個top變量來指示棧頂元素在數組中的位置。
(2)當棧存在一個元素的時候,top等於0(即數組中的a0,下標爲0),所以我們初始化棧時,必須將空棧top=-1,這是空棧的判定條件。

而棧的鏈式存儲做的問題,我們一般用含尾指針的單鏈表就可以解決,所以鏈棧顯得不重要,則不再贅述。

2、棧的進棧出棧操作

/**
 * 若棧存在,插入新元素e到S中作爲棧頂元素
 * @param S 棧S
 * @param e 插入元素
 * @return 狀態值
 */
Status push(SqStack* S,SElemType e) {
    //棧滿了
    if (S->top == MAXSIZE - 1) {
        return ERROR;
    }
    
    S->top++;
    S->data[S->top] = e;
    return OK;
}

/**
 * 刪除S中的棧頂元素,用e來接收
 * @param S 棧S
 * @param e 接收的棧頂元素
 * @return 狀態值
 */
Status pop(SqStack* S, SElemType* e) {
    //棧爲空
    if (S->top == -1) {
        return ERROR;
    }
    
    *e = S->data[S->top];
    S->top--;
    return OK;
}

3、兩棧共享空間

兩個相同的棧,我們可以使用一個數組進行存儲兩個棧,只不過他們在一塊數組裏。這樣一個棧增加另一個棧縮短的數據結構一般例如買賣股票,你買入,就會有人賣出。

做法:數組有兩個端口(0和MAXSIZE - 1),兩個棧如果增加元素,就是兩個端口向中間延伸。top1和top2分別指向-1和MAXSIZE。

/**
 * 兩棧共享空間
 */
typedef struct SqDoubleStack {
    
    SElemType data[MAXSIZE];
    //棧1和棧2的棧頂位置
    int top1;
    int top2;
}SqDoubleStack;

注意:棧1空和棧2空的條件簡單,即top1 = -1或者top = MAXSIZE。但是棧滿的條件(即兩個指針向中間靠攏)即top1 + 1 == top2

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

/**
 * 若棧存在,插入新元素e到S中作爲棧頂元素
 * @param S 棧S
 * @param e 插入元素
 * @param stacknumber 哪個棧入棧
 * @return 狀態值
 */
Status push(SqDoubleStack* S,SElemType e, int stacknumber) {
    
    //棧滿了
    if (S->top1 + 1 == S->top2) {
        return ERROR;
    }
    
    //分情況進棧
    if (stacknumber == 1) {
        S->top1++;
        S->data[S->top1] = e;
    } 
    else if (stacknumber == 2) {
        S->top2--;
        S->data[S->top2] = e;
    }

    return OK;
}

/**
 * 刪除S中的棧頂元素,用e來接收
 * @param S 棧S
 * @param e 接收的棧頂元素
 * @param stacknumber 哪個棧出棧
 * @return 狀態值
 */
Status pop(SqDoubleStack* S, SElemType* e, int stacknumber) {
    
    //因爲兩棧空,判斷條件不同
    if (stacknumber == 1) {
        //棧1空
        if (S->top1 == -1) {
            return ERROR;
        }
        *e = S->data[S->top1];
        S->top1--;
    } 
    else if (stacknumber == 2) {
        //棧2空
        if (S->top2 == MAXSIZE) {
            return ERROR;
        }
        *e = S->data[S->top2];
        S->top2++;
    }

    return OK;
    
}

三、隊列

定義:是隻允許在一端插入,另一端進行刪除的線性表。太常見了,就不舉例了。

隊列的抽象數據類型ADT(書p112),其代碼表示:

//在queue.h頭文件中
#include <stdio.h>

//定義隊列長度大小
#define MAXSIZE 100

//增強代碼可讀性
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int QElemType;
typedef int Status;


/**
 * 隊列的聲明
 */
typedef struct queue {
    //數據元素
    QElemType data[MAXSIZE];
    //頭尾指針
    int front;
    int rear;
}SqQueue;

//隊列的CRUD功能聲明
void initQueue(SqQueue* Q); /*1.初始化一個空隊列Q*/
Status isEmpty(SqQueue Q); /*2.判斷隊列是否爲空,空返回true*/
void clearQueue(SqQueue* Q); /*3.清空隊列*/
void destroyQueue(SqQueue* Q); /*4.若隊列存在,則銷燬它*/
Status getHead(SqQueue Q, QElemType* e); /*5.若隊列存在非空,就用e返回Q的隊頭元素*/
Status enQueue(SqQueue* Q, QElemType e); /*6.若隊列存在,插入新元素e到隊列的隊尾*/
Status deQueue(SqQueue* Q, QElemType* e); /*7.刪除隊列的隊頭元素,用e返回其值*/
int queueLength(SqQueue Q); /*8.返回隊列Q的元素個數*/

1、順序存儲的不足

隊列的順序存儲與數組類似,只不過在隊尾加上rear(末尾下標),聲明就不再贅述。

不足:隊列元素的出列是在隊頭,下標爲front的位置(即隊頭不是一直是0)。隊列元素的入隊在隊尾,下標爲rear位置。rear一直入隊,front一直出隊,可能當rear都到了MAXSIZE之外了(數組越界),但是front前面還有很多空缺,就要就出現了 “假溢出”。

2、循環隊列

解決上述假溢出的辦法就是後面滿了,就在前面補上,頭尾相接的循環隊列。

循環隊列的順序存儲結構

/**
 * 循環隊列聲明
 */
typedef struct queueInfo {
    //數據元素
    QElemType data[MAXSIZE];
    //頭尾指針
    int front;
    int rear; /*rear指向隊尾的下一個位置(即將插入的位置)*/
}QueueInfo;
注意:在求循環隊列長度和入隊出隊操作**需要注意。
(1)循環隊列長度: length = (Q.rear - Q.front + MAXSIZE) % MAXSIZE
(2)循環隊列的隊列滿條件:((Q->rear + 1) % MAXSIZE == Q->front)3)循環隊列的隊列空條件:(Q->front == Q->rear)

代碼如下:

/**
 * 返回隊列Q的元素個數
 * @param Q 循環隊列
 * @return 元素個數
 */
int queueLength(SqQueue Q) {
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

/**
 * 若隊列存在且未滿,插入新元素e到隊列的隊尾
 * @param Q 循環隊列
 * @param e 插入元素
 * @return 狀態值
 */
Status enQueue(SqQueue* Q, QElemType e) {
    //如果隊列滿了
    if ((Q->rear + 1) % MAXSIZE) {
        return ERROR;
    }
    
    //rear指向後一位(即將插入的位置)
    Q->data[Q->rear] = e;
    Q->rear = (Q->rear + 1) % MAXSIZE;//如果假溢出,就到數組前面去
    
    return OK;
}

/**
 * 刪除隊列的隊頭元素,用e返回其值
 * @param Q 循環隊列
 * @param e 刪除的元素值
 * @return 狀態值
 */
Status deQueue(SqQueue* Q, QElemType* e) {
    //如果隊列空了
    if (Q->front == Q->rear) {
        return ERROR;
    }
    
    *e = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE; //同理

    return OK;
}

3、隊列的鏈式存儲

而隊列的鏈式存儲其實就是head指針位置取元素和rear指針位置加元素的單鏈表,這裏則不再贅述。

需要注意的是:
鏈隊列的結構代碼中

typedef struct QNode {
    QElemType data;
    struct QNode* next;
}QNode,*QueuePtr;

//QueuePtr爲指向結點的指針

//這個結構裏面包含兩種QueuePtr(隊頭、隊尾指針)
typedef struct queuePtr {
    QueuePtr front, rear;
}LinkQueue;

故以下幾種表示的含義爲:
在這裏插入圖片描述

例如:LinkQueue * Q中
Q->rear->data表示爲:Q這個鏈隊列所指向的尾指針所指向的節點數據
Q->front->next同理爲:Q這個鏈隊列所指向的頭指針所指向的節點的後繼節點

四、作業

1、在數據結構中,從邏輯上可以把數據結構分爲( )
A. 動態結構和靜態結構
B. 緊湊結構和非緊湊結構
C. 線性結構和非線性結構
D. 內部結構和外部結構

2、在存儲數據時,通常不僅要存儲各數據元素的值,而且還要存儲( )
A. 數據的處理方法
B. 元素的類型
C. 數據元素之間的關係
D. 數據存儲的方法

3、與單鏈表相比,雙鏈表的優點之一是( )
A. 插入、刪除操作更簡單
B. 可以進行隨機訪問
C. 可以省略表頭指針或表尾指針
D. 順序訪問相鄰節點更靈活

4、如果對線性表的操作只有兩種,即刪除第一個元素、在最後一個元素噹噹後面插入新元素,則最好使用( )
A. 只有表頭指針沒有表尾指針的循環單鏈表
B. 只有表尾指針沒有表頭指針的循環單鏈表
C. 非循環雙鏈表
D. 循環雙鏈表

5、一個棧的進棧順序是a、b、c、d、e,則棧不可能出現的輸出順序是( )
A. edcba
B. decba
C. dceab
D. abcde

6、若已知一個進棧序列是1、2、3、···· · · 、n,則其輸出序列爲p1、p2、p3、· · · 、pi、· · · 、pn,若p1 = n,則pi爲( )
A. i
B. n-i
C. n-i+1
D. 不確定

7、輸入序列爲ABC,可變爲CBA時,經過的棧操作爲( )
A. push, pop, push, pop, push, pop
B. push, push, push, pop, pop, pop
C. push, push, pop, pop, push, pop,
D. push, pop, push, push, pop, pop

8、若採用順序棧存儲方式存儲,現兩棧共享空間V[1 m],top[1], top[2]分別代表第1個和第2個棧的棧頂,棧1的底在V[1], 棧2的底在V[m],則棧滿的條件是( )
A.|top[2] – top[1]| = 0
B.top[1] + 1 = top[2]
C.top[1] + top[2] = m
D.top[1] = top[2]

9、數組A中,每個元素的長度爲3個字節,行下標i從1到8,列下標從1到10,從首地址SA開始連續存放的存儲器內,該數組按行進行存放,元素A[8][5]的起始地址爲( )
A. SA+141
B. SA+144
C. SA+222
D. SA+255

10、循環隊列用數組存放其元素值A[0,m-1], 已知其頭尾指針分別是rear和front,則當前隊列的元素個數是( )
A. rear-front+1
B. rear-front-1
C. rear-front
D. (rear-front+m)%m

11、判斷一個循環隊列QU(最多元素爲m0)爲滿的條件是( )
A. QU.front == QU.rear
B. QU.front != QU.rear
C. QU.front == (QU.rear + 1)%m0
D. QU.front == QU.rear – 1

12、利用棧實現十進制到二進制的轉換(利用除2取餘法)
輸入:45
輸出:10 1101

13、循環隊列的應用——舞伴配對問題:在舞會上,男、女各自排成一隊。舞會開始時,依次從男隊和女隊的隊頭各出一人配成舞伴。如果兩隊初始人數不等,則較長的那一隊中未配對者等待下一輪舞曲。假設初始男、女人數及性別已經固定,舞會的輪數從鍵盤輸入。試模擬解決上述舞伴配對問題。要求:從屏幕輸出每一輪舞伴配對名單,如果在該輪有未配對的,能夠從屏幕顯示下一輪第一個出場的未配對者的姓名。(分別依次輸入男姓名、女姓名,並輸出每輪配對結果,以下爲一次標準輸入輸出,兩隊人數由主函數固定)
輸入第1個男人名:a
輸入第2個男人名:b
輸入第3個男人名:c
輸入第4個男人名:d
輸出男人隊列:a b c d

輸入第1個女人名:A
輸入第2個女人名:B
輸入第3個女人名:C
輸入第4個女人名:D
輸入第5個女人名:E
輸入第6個女人名:F
輸出女人隊列:A B C D E F
配對者:a–A
配對者:b–B
配對者:c–C
配對者:d–D
未配對的第一個出來的是:E
配對者:a–E
配對者:b–F
配對者:c–A
配對者:d–B
未配對的第一個出來的是:C
配對者:a–C
配對者:b–D
配對者:c–E
配對者:d–F
未配對的第一個出來的是:A

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章