資料結構(線性表)
線性結構-線性表
實驗簡介
資料結構中的邏輯結構分為線性結構和非線性結構,這一章和下一章我們會介紹線性結構,簡單地說,線性結構是n個數據元素的有序(次序)集合,它有下列幾個特徵:
1.集合中必存在唯一的一個"第一個元素";
2.集合中必存在唯一的一個"最後的元素";
3.除最後元素之外,其它資料元素均有唯一的"後繼";
4.除第一元素之外,其它資料元素均有唯一的"前驅"。
這一章我們就來講解線性結構中線性表,它是最常用且最簡單的一種資料結構。線性表是一個含有n≥0個結點的有限序列,對於其中的結點,有且僅有一個開始結點沒有前驅但有一個後繼結點,有且僅有一個終端結點沒有後繼但有一個前驅結點,其它的結點都有且僅有一個前驅和一個後繼結點。一般地,一個線性表可以表示成一個線性序列:k1,k2,…,kn,其中k1是開始結點,kn是終端結點。
一般線性表包含下列基本操作:初始化、銷燬、重置為空表、判斷是否為空、獲取長度、根據位置獲取對應元素、查詢元素、獲取指定元素的前驅和後繼元素、插入元素、刪除元素、遍歷元素。
1. 線性表的順序表示和實現
線性表的順序表示指的是用物理上的一段連續的地址來儲存資料元素,如下圖所示。如果第一個元素的在記憶體上的地址為a1,每個元素佔用的空間是l,那麼第n個元素的地址就是a1+(n-1) x l。
只要確定了第一個元素的地址,那麼我們可以對線性表中的任一元素隨機存取,由於程式語言中的陣列也有隨機存取的特點,下面就用陣列來描述線性表的順序儲存結構。
下面是程式碼實現:
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INIT_SIZE 10 //初始化表長
#define INCREMENT_SIZE 5 //分配增量
typedef int Status;
typedef int Elemtype;
/*
* 儲存結構
*/
typedef struct
{
Elemtype *elem; //儲存空間基址
int length; //當前長度
int size; //當前分配的表長大小
}SqList;
/*
* 初始化一個空的線性表
*/
Status InitList(SqList *L)
{
L->elem = (Elemtype *) malloc(INIT_SIZE * sizeof(Elemtype));
if (!L->elem)
{
return ERROR;
}
L->length = 0;
L->size = INIT_SIZE;
return OK;
}
/*
* 銷燬線性表
*/
Status DestroyList(SqList *L)
{
free(L->elem);
L->length = 0;
L->size = 0;
return OK;
}
/*
* 清空線性表
*/
Status ClearList(SqList *L)
{
L->length = 0;
return OK;
}
/*
* 判斷線性表是否為空
*/
Status isEmpty(const SqList L)
{
if (0 == L.length)
{
return TRUE;
}
else
{
return FALSE;
}
}
/*
* 獲取長度
*/
Status getLength(const SqList L)
{
return L.length;
}
/*
* 根據位置獲取元素
*/
Status GetElem(const SqList L, int i, Elemtype *e)
{
if (i < 1 || i > L.length)
{
return ERROR;
}
*e = L.elem[i-1];
return OK;
}
/*
* 比較兩個元素是否相等
*/
Status compare(Elemtype e1, Elemtype e2)
{
if (e1 == e2)
{
return 0;
}
else if (e1 < e2)
{
return -1;
}
else
{
return 1;
}
}
/*
* 查詢元素
*/
Status FindElem(const SqList L, Elemtype e, Status (*compare)(Elemtype, Elemtype))
{
int i;
for (i = 0; i < L.length; i++)
{
if (!(*compare)(L.elem[i], e))
{
return i + 1;
}
}
if (i >= L.length)
{
return ERROR;
}
}
/*
* 查詢前驅元素
*/
Status PreElem(const SqList L, Elemtype cur_e, Elemtype *pre_e)
{
int i;
for (i = 0; i < L.length; i++)
{
if (cur_e == L.elem[i])
{
if (i != 0)
{
*pre_e = L.elem[i - 1];
}
else
{
return ERROR;
}
}
}
if (i >= L.length)
{
return ERROR;
}
}
/*
* 查詢後繼元素
*/
Status NextElem(const SqList L, Elemtype cur_e, Elemtype *next_e)
{
int i;
for (i = 0; i < L.length; i++)
{
if (cur_e == L.elem[i])
{
if (i < L.length - 1)
{
*next_e = L.elem[i + 1];
return OK;
}
else
{
return ERROR;
}
}
}
if (i >= L.length)
{
return ERROR;
}
}
/*
* 插入元素
*/
Status InsertElem(SqList *L, int i, Elemtype e)
{
Elemtype *new;
if (i < 1 || i > L->length + 1)
{
return ERROR;
}
if (L->length >= L->size)
{
new = (Elemtype*) realloc(L->elem, (L->size + INCREMENT_SIZE) * sizeof(Elemtype));
if (!new)
{
return ERROR;
}
L->elem = new;
L->size += INCREMENT_SIZE;
}
Elemtype *p = &L->elem[i - 1];
Elemtype *q = &L->elem[L->length - 1];
for (; q >= p; q--)
{
*(q + 1) = *q;
}
*p = e;
++L->length;
return OK;
}
/*
* 刪除元素並返回其值
*/
Status DeleteElem(SqList *L, int i, Elemtype *e)
{
if (i < 1 || i > L->length)
{
return ERROR;
}
Elemtype *p = &L->elem[i - 1];
*e = *p;
for (; p < &L->elem[L->length]; p++)
{
*(p) = *(p + 1);
}
--L->length;
return OK;
}
/*
* 訪問元素
*/
void visit(Elemtype e)
{
printf("%d ", e);
}
/*
* 遍歷線性表
*/
Status TraverseList(const SqList L, void (*visit)(Elemtype))
{
int i;
for(i = 0; i < L.length; i++)
{
visit(L.elem[i]);
}
return OK;
}
/*
* 主函式測試
*/
int main()
{
SqList L;
if (InitList(&L))
{
Elemtype e;
printf("init_success\n");
int i;
for (i = 0; i < 10; i++)
{
InsertElem(&L, i + 1, i);
}
printf("length is %d\n", getLength(L));
if (GetElem(L, 1, &e)) {
printf("The first element is %d\n", e);
}
else
{
printf("element is not exist\n");
}
if (isEmpty(L))
{
printf("list is empty\n");
}
else
{
printf("list is not empty\n");
}
printf("The 5 at %d\n", FindElem(L, 5, *compare));
PreElem(L, 6, &e);
printf("The 6's previous element is %d\n", e);
NextElem(L, 6, &e);
printf("The 6's next element is %d\n", e);
DeleteElem(&L, 1, &e);
printf("delete first element is %d\n", e);
printf("list:");
TraverseList(L,visit);
if (DestroyList(&L))
{
printf("\ndestroy_success\n");
}
}
}
2. 線性表的鏈式表示和實現
上一節討論了線性表的順序表示和實現,這一節我們來討論線性表的鏈式表示和實現,線性表的順序儲存結構是邏輯位置和物理位置都相鄰,而鏈式儲存結構是邏輯位置相鄰,但物理位置不一定相鄰,相比順序儲存結構,它不能隨機存取,但在插入和刪除操作時不需要移動元素,大大提高了增加和刪除元素的效率。
通常鏈式儲存結構會有一個個結點組成,結點中包含兩個域一個是資料域,一個是指標域,資料域中儲存資料,指標域中儲存下一個後繼元素的地址,如下圖所示,這一個個結點組成連結串列,也稱線性連結串列或單鏈表。
單鏈表的邏輯結構如下圖所示
除了單鏈表之外還有迴圈連結串列和雙向連結串列,迴圈連結串列的特點是最後一個結點的指標指向頭結點,形成一個環,雙向連結串列的特點是結點中多了一個指向前驅元素的指標,這兩種連結串列的邏輯結構如下面兩張圖所示
迴圈連結串列
雙向連結串列
這裡主要程式碼實現一下單鏈表:
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int ElemType;
typedef int Status;
/*
* 儲存結構
*/
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
/*
* 初始化線性表
*/
void InitList(LinkList *L)
{
*L = (LinkList) malloc(sizeof(LNode));
if (!L)
{
exit(OVERFLOW);
}
(*L)->next = NULL;
}
/*
* 銷燬線性表
*/
void DestroyList(LinkList *L)
{
LinkList temp;
while (*L)
{
temp = (*L)->next;
free(*L);
*L = temp;
}
}
/*
* 清空線性表
*/
void ClearList(LinkList L)
{
LinkList p = L->next;
L->next = NULL;
DestroyList(&p);
}
/*
* 判斷是否為空
*/
Status isEmpty(LinkList L)
{
if (L->next)
{
return FALSE;
}
else
{
return TRUE;
}
}
/*
* 獲取長度
*/
int GetLength(LinkList L)
{
int i = 0;
LinkList p = L->next;
while (p)
{
i++;
p = p->next;
}
return i;
}
/*
* 根據位置獲取元素
*/
Status GetElem(LinkList L, int i, ElemType *e)
{
int j = 1;
LinkList p = L->next;
while (p && j < i)
{
j++;
p = p->next;
}
if (!p || j > i)
{
return ERROR;
}
*e = p->data;
return OK;
}
/*
* 比較兩個元素是否相等
*/
Status compare(ElemType e1, ElemType e2)
{
if (e1 == e2)
{
return 0;
}
else if (e1 < e2)
{
return -1;
}
else
{
return 1;
}
}
/*
* 查詢指定元素的位置
*/
int FindElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType))
{
int i = 0;
LinkList p = L->next;
while (p)
{
i++;
if (!compare(p->data, e))
{
return i;
}
p = p->next;
}
return 0;
}
/*
* 獲取前驅元素
*/
Status PreElem(LinkList L, ElemType cur_e, ElemType *pre_e)
{
LinkList q, p = L->next;
while (p->next)
{
q = p->next;
if (q->data == cur_e)
{
*pre_e = p->data;
return OK;
}
p = q;
}
return ERROR;
}
/*
* 獲取後繼元素
*/
Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e)
{
LinkList p = L->next;
while (p->next)
{
if (p->data == cur_e)
{
*next_e = p->next->data;
return OK;
}
p = p->next;
}
return ERROR;
}
/*
* 插入元素
*/
Status InsertElem(LinkList L, int i, ElemType e)
{
int j = 0;
LinkList s, p = L;
while (p && j < i - 1)
{
j++;
p = p->next;
}
if (!p || j > i - 1)
{
return ERROR;
}
s = (LinkList) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
/*
* 刪除元素並返回值
*/
Status DeleteElem(LinkList L, int i, ElemType *e)
{
int j = 0;
LinkList q, p = L;
while (p->next && j < i - 1)
{
j++;
p = p->next;
}
if (!p->next || j > i - 1)
{
return ERROR;
}
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
/*
* 訪問元素
*/
void visit(ElemType e)
{
printf("%d ", e);
}
/*
* 遍歷線性表
*/
void TraverseList(LinkList L, void (*visit)(ElemType))
{
LinkList p = L->next;
while (p)
{
visit(p->data);
p = p->next;
}
}
int main()
{
LinkList L;
InitList(&L);
ElemType e;
int i;
if (L)
{
printf("init success\n");
}
if (isEmpty(L))
{
printf("list is empty\n");
}
for (i = 0; i < 10; i++)
{
InsertElem(L, i + 1, i);
}
if (GetElem(L, 1, &e)) {
printf("The first element is %d\n", e);
}
printf("length is %d\n", GetLength(L));
printf("The 5 at %d\n", FindElem(L, 5, *compare));
PreElem(L, 6, &e);
printf("The 6's previous element is %d\n", e);
NextElem(L, 6, &e);
printf("The 6's next element is %d\n", e);
DeleteElem(L, 1, &e);
printf("delete first element is %d\n", e);
printf("list:");
TraverseList(L,visit);
DestroyList(&L);
if (!L) {
printf("\ndestroy success\n");
}
}
3. 小結
這一章我們講解了線性結構中線性表的順序及鏈式的表示和實現,順序儲存結構中的元素在邏輯位置和物理位置上都相鄰,鏈式儲存結構中的元素在邏輯位置上相鄰,但在物理位置上不一定相鄰,順序儲存結構讀取元素的效率比較高,鏈式儲存結構新增和刪除元素的效率比較高。鏈式儲存結構除了單鏈表之外,還有迴圈連結串列和雙向連結串列。
線性結構-棧和佇列
實驗簡介
前一章我們講了線性結構中的線性表,這一章我們來講解線性結構中的棧和佇列,其實棧和佇列也是線性表,只是它們是操作受限的線性表。
一、棧
首先我們來講講棧,棧是隻能在表尾進行插入或刪除操作的線性表,通常我們稱表尾端為棧頂,表頭端為棧底,它是一種先進後出的線性表,既只能在表尾端插入元素,稱為入棧,也只能在表尾端刪除元素,稱為退棧,如下圖所示
棧既然也是線性表,那麼它也有順序儲存結構和鏈式儲存結構兩種表示方法,這兩種表示方法實現類似,我們這裡講解一下順序儲存結構的程式碼實現:
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define INIT_SIZE 20
#define INCREMENT_SIZE 5
typedef int SElemType;
typedef int Status;
/*
* 儲存結構
*/
typedef struct
{
SElemType *base; //棧尾指標
SElemType *top; //棧頂指標
int size; //棧的大小
}SqStack;
/*
* 初始化棧
*/
Status InitStack(SqStack *S)
{
S->base = (SElemType*) malloc(INIT_SIZE * sizeof(SElemType));
if (!S->base)
{
exit(OVERFLOW);
}
S->top = S->base;
S->size = INIT_SIZE;
return OK;
}
/*
* 銷燬棧
*/
Status DestroyStack(SqStack *S)
{
free(S->base);
S->base = NULL;
S->top = NULL;
S->size = 0;
return OK;
}
/*
* 清空棧
*/
Status ClearStack(SqStack *S)
{
S->top = S->base;
return OK;
}
/*
* 判斷棧是否為空
*/
Status IsEmpty(SqStack S)
{
if (S.top == S.base)
{
return TRUE;
}
else
return FALSE;
}
/*
* 獲取棧的長度
*/
int GetLength(SqStack S)
{
return S.top - S.base;
}
/*
* 獲取棧頂元素
*/
Status GetTop(SqStack S, SElemType *e)
{
if (S.top > S.base)
{
*e = *(--S.top);
return OK;
}
else
{
return ERROR;
}
}
/*
* 壓棧
*/
Status Push(SqStack *S, SElemType e)
{
if ((S->top - S->base) / sizeof(SElemType) >= S->size)
{
S->base = (SElemType*) realloc(S->base, (S->size + INCREMENT_SIZE) * sizeof(SElemType));
if (!S->base)
{
exit(OVERFLOW);
}
S->top = S->base + S->size;
S->size += INCREMENT_SIZE;
}
*S->top = e;
S->top++;
return OK;
}
/*
* 退棧
*/
Status Pop(SqStack *S, SElemType *e)
{
if (S->top == S->base)
{
return ERROR;
}
S->top--;
*e = *S->top;
return OK;
}
/*
* 訪問元素
*/
void visit(SElemType e)
{
printf("%d ", e);
}
/*
* 遍歷棧
*/
Status TraverseStack(SqStack S, void (*visit)(SElemType))
{
while (S.top > S.base)
{
visit(*S.base);
S.base++;
}
return OK;
}
int main()
{
SqStack S;
if (InitStack(&S))
{
SElemType e;
int i;
printf("init_success\n");
if (IsEmpty(S))
{
printf("Stack is empty\n");
}
for (i = 0; i < 10; i++)
{
Push(&S, i);
}
GetTop(S, &e);
printf("The first element is %d\n", e);
printf("length is %d\n", GetLength(S));
Pop(&S, &e);
printf("Pop element is %d\n", e);
TraverseStack(S, *visit);
if (DestroyStack(&S))
{
printf("\ndestroy_success\n");
}
}
}
通過棧可以解決很多問題,例如數值轉換、括號匹配、迷宮求解、表示式求值和漢諾塔等等問題。
二、佇列
上面我們講了棧,接下來我們講下佇列,佇列剛好和棧相反,它是一種先進先出的線性表,只能在一端插入元素,在另一端刪除元素,如下圖所示,允許插入元素的一端稱為隊尾,允許刪除元素的一端稱為隊頭。
佇列也一樣有順序和鏈式儲存結構兩種表示方法,前面的棧我們實現了順序儲存結構,這裡我們就程式碼實現下佇列的鏈式儲存結構:
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int QElemType;
typedef int Status;
/*
* 儲存結構
*/
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct
{
QueuePtr front; //隊頭指標
QueuePtr rear; //隊尾指標
}LinkQueue;
/*
* 初始化佇列
*/
Status InitQueue(LinkQueue *Q)
{
Q->front = Q->rear = (QueuePtr) malloc(sizeof(QNode));
if (!Q->front)
{
exit(OVERFLOW);
}
Q->front->next = NULL;
return OK;
}
/*
* 銷燬佇列
*/
Status DestroyQueue(LinkQueue *Q)
{
while (Q->front)
{
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
/*
* 清空佇列
*/
Status ClearQueue(LinkQueue *Q)
{
DestroyQueue(Q);
InitQueue(Q);
return OK;
}
/*
* 判斷佇列是否為空
*/
Status IsEmpty(LinkQueue Q)
{
if (Q.front->next == NULL)
{
return TRUE;
}
else
{
return FALSE;
}
}
/*
* 獲取佇列的長度
*/
int GetLength(LinkQueue Q)
{
int i = 0;
QueuePtr p = Q.front;
while (Q.rear != p)
{
i++;
p = p->next;
}
return i;
}
/*
* 獲取隊頭元素
*/
Status GetHead(LinkQueue Q, QElemType *e)
{
QueuePtr p;
if (Q.front == Q.rear)
{
return ERROR;
}
p = Q.front->next;
*e = p->data;
return OK;
}
/*
* 入隊
*/
Status EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr p = (QueuePtr) malloc(sizeof(QNode));
if (!p)
{
exit(OVERFLOW);
}
p->data = e;
p->next = NULL;
Q->rear->next = p;
Q->rear = p;
return OK;
}
/*
* 出隊
*/
Status DeQueue(LinkQueue *Q, QElemType *e)
{
QueuePtr p;
if (Q->front == Q->rear)
{
return ERROR;
}
p = Q->front->next;
*e = p->data;
Q->front->next = p->next;
if (Q->rear == p)
{
Q->rear = Q->front;
}
free(p);
return OK;
}
/*
* 訪問元素
*/
void visit(QElemType e)
{
printf("%d ", e);
}
/*
* 遍歷佇列
*/
Status TraverseQueue(LinkQueue Q, void (*visit)(QElemType))
{
QueuePtr p = Q.front->next;
while (p)
{
visit(p->data);
p = p->next;
}
return OK;
}
int main()
{
LinkQueue Q;
if (InitQueue(&Q))
{
QElemType e;
int i;
printf("init_success\n");
if (IsEmpty(Q))
{
printf("queue is empty\n");
}
for (i = 0; i < 10; i++)
{
EnQueue(&Q, i);
}
GetHead(Q, &e);
printf("The first element is %d\n", e);
printf("length is %d\n", GetLength(Q));
DeQueue(&Q, &e);
printf("delete element is %d\n", e);
TraverseQueue(Q, *visit);
if (DestroyQueue(&Q))
{
printf("\ndestroy_success\n");
}
}
}
上面實現的是鏈式儲存結構,使用順序儲存結構可以實現迴圈佇列,有興趣的童鞋可以查查資料哦