嵌入式團隊培訓_線性結構
一、線性表
定義:零個或多個數據元素的有限序列
要求:第一個元素無前驅,最後一個元素無後繼,其他元素都有且只有一個前驅和後繼。
思考:循環鏈表?雙向鏈表?
線性表的抽象數據類型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