數據結構 - 線性表

線性表的基本概念

線性表是由n(n >= 0)個數據元素(結點)組成的有限序列.node

則n爲表的長度, 當n = 0時爲空表算法

非空的線性表(n > 0)的表示方式

L = (a1, a1, …, an)數組

其中a1起始結點, an終端結點. 對任意一對相鄰的結點ai和ai+1 (1 <= i < n), ai稱爲ai+1直接前驅, ai+1稱爲ai直接後繼.ui

在這張圖片中, 線性表是一個由10個結點組成的有限序列. 表的長度爲10, 結點A爲起始結點, 結點J爲終端結點. 對於任意相鄰的兩個結點, 例如AB兩個結點, 結點A爲結點B的直接前驅, 結點B爲結點A的直接後繼.spa

特色

  1. 線性表中只有一個起始結點, 一個終端結點
  2. 起始結點沒有直接前驅, 有一個直接後繼
  3. 終端結點沒有直接後繼, 有一個直接前驅
  4. 除了起始結點和終端結點, 每一個結點都有且只有一個直接前驅和一個直接後繼

線性表的順序存儲

將表中的結點依次存放在計算機內存中一組連結的存儲單元中. 用順序存儲方式實現的線性表稱爲順序表. 通常使用數組來表示順序表.3d

已知a1地址爲locate(a1), 每一個數據佔m個存儲單元, 則ai的地址指針

locate(ai) = locate(a1) + m * (i - 1)code

順序存儲線性表須要存儲

線性表的大小: maxSizecdn

線性表的長度: lengthblog

所存放的數據類型: DataType

注意

  1. 順序表是用一維數組實現的線性表, 數組的下標能夠當作是元素的相對地址.
  2. 邏輯上相鄰的元素, 存儲在物理位置也相鄰的單元中

特色

  1. 線性表的邏輯結構與存儲結構一致.
  2. 能夠對數據元素實現隨機讀取

基本運算在順序表上的實現

插入

線性表的插入運算是指在表的第 i (1 <= i <= n+1) 個位置上, 插入一個新結點, 使長度爲n的線性表變成長度爲n + 1的線性表

前提: 線性表的長度爲n, 要插入的位置爲i

元素的移動次數: 在i位置插入數據時, 要移動 n - i + 1 個數據元素

平均移動元素個數: 咱們能夠簡單理解爲最好的時候, 在末尾插入, 移動0個數據元素, 最壞的時候, 在位置爲1的地插入, 移動n個數據元素, 則平均移動元素個數爲 n / 2, 則時間複雜度爲O(n)

注意事項

1. 當表空間已滿, 不可再作插入操做
2. 當插入位置爲非法位置, 不可作正常插入操做
複製代碼

代碼

/** 線性表的插入 @param pList 類型爲SeqList的結構體指針 @param x 要插入的類型爲DataType類型的數據元素 @param i 要插入的位置 */
void insertSeqList(SeqList * pList, DataType x, int i) {
    // 判斷線性表是否已滿
    if (pList->length == maxSize) {
        printf("表已滿 \n");
        return;
    }
    
    // 檢查插入位置是否合法
    if (i < 1 || i > (pList->length) + 1) {
        printf("插入位置不合法 \n");
        return;
    }
    
    // 進行插入操做
    // 從表的最後一個位置依次進行後移
    for (int j = pList->length; j >= i; j--) {
        pList->data[j] = pList->data[j-1];
    }
    
    // 後移完成以後, 對空出的位置進行賦值
    pList->data[i - 1] = x;
    
    // 表的長度加1
    pList->length++;
}
複製代碼

刪除

線性表的刪除運算是指將表的第 i 個結點刪去, 使長度爲 n 的線性表變成長度爲 n-1 的線性表

注意事項

  1. 當要刪除元素的位置 i 不在表長範圍內 (i < 1 || i > L -> length) 時, 爲非法位置, 不能作正常的刪除操做

代碼

/** 線性表的刪除 @param pList 類型爲SeqList結構體的指針 @param i 要刪除的位置 */
void deleteSeqList(SeqList * pList, int i) {
    
    // 檢查刪除位置是否合法
    if (i < 1 || i > pList -> length) {
        printf("刪除位置不合法");
        return;
    }
    
    // 進行刪除操做, 依次左移
    for (int j = i; j < pList->length; j++) {
        pList->data[j - 1] = pList->data[j];
    }
    
    // 表的長度減1
    pList->length--;
}
複製代碼

元素的移動次數: 在i位置刪除數據時, 要移動 n - i 個數據元素

平均移動元素個數: 咱們能夠簡單理解爲最好的時候, 在末尾刪除, 移動0個數據元素, 最壞的時候, 在位置爲1的地刪除, 移動 n-1 個數據元素, 則平均移動元素個數爲 (n-1) / 2, 則時間複雜度爲O(n)

定位 / 查找

定位運算的功能是求L中值等於x的結點序號的最小值, 當不存在這種結點時結果爲0

/** 線性表的定位 @param pList 類型爲SeqList結構體的指針 @param name 要查找的姓名 @return 查找到的元素的位置 */
int locateSeqList(SeqList * pList, char name[]) {
    int i = 0;
    
    // 在順序表中查找值爲name的結點
    while (i < pList->length && pList->data[i].name == name) {
        i++;
    }
    
    // 若找到值爲name的結點, 則返回結點的序號
    if (i < pList->length) {
        return i + 1;
    } else { // 未找到值爲name的結點, 返回0
        return 0;
    }
}
複製代碼

總結

  1. 插入算法 , 時間複雜度爲O(n). 通常狀況下, 元素的移動次數爲 n-i+1次, 平均移動次數爲 n / 2
  2. 刪除算法, 時間複雜度爲O(n). 元素的平均移動次數爲 (n-1) / 2
  3. 定位算法, 比較操做的時間複雜度爲O(n). 求表長和讀表元素算法的時間複雜度爲O(1)

順序表的優勢

  1. 無需爲表示結點之間的邏輯關係而增長額外存儲空間
  2. 能夠方便地隨機存取表中的任一結點

順序表的缺點

  1. 插入和刪除運算不方便, 必須移動大量的結點
  2. 順序表要求佔用連結的空間, 當表長變化較大時, 難以肯定合適的存儲規模

線性表的連接存儲

連接方式存儲的線性表稱爲鏈表. Link List

特色

  1. 用一組任意的存儲單元來存放
  2. 鏈表中結點的邏輯次序和物理次序不必定相同. 還必須存儲指示其後繼結點的地址信息

結點的結構

{% asset_img node.png %}

數據域, 又爲data域, 存放結點中的數據

指針域, 又爲next域, 存放結點的直接後繼的地址

單鏈表的類型定義

全部結點經過指針連接而組成單鏈表.

NULL稱爲爲空指針

Head稱爲頭指針, 存放鏈表中的第一個結點的地址

單鏈表中第一個結點內通常不存數據, 稱爲爲頭結點

常見的單鏈表的四種形式

單鏈表的特色

  1. 起始結點又稱爲首結點. 沒有直接前驅, 故設頭指針head指向首結點
  2. 鏈表由頭指針惟一肯定, 單鏈表能夠用頭指針的名字來命名.
  3. 終端結點又稱爲尾結點. 沒有直接後繼, 故終端結點的指針域爲空, 即NULL
  4. 除頭結點以外的結點爲表結點
  5. 爲運算操做方便, 頭結點中不存數據

單鏈表的初始化

// 聲明一個結構體類型
typedef struct {
    char name[12];
    int age;
} DataType;

// 聲明一個單鏈表類型
typedef struct node {
    DataType data; // 數據域
    struct node * next; // 指針域
} Node, * LinkList;

/** 初始化一個單鏈表 @return 初始化好的單鏈表 */
LinkList initiateLinkList() {
    
    // 頭指針
    LinkList head;
    
    // 動態建立一個結點, 也就是頭結點, 而後讓頭指針指向這個結點
    head = malloc(sizeof(Node));
    
    // 頭結點的指針域爲NULL
    head->next = NULL;
    
    // 返回這個單鏈表
    return head;
}
複製代碼

插入

插入運算是將值爲x的新結點 插入到表的第i個結點的位置上, 即插入到ai-1與ai之間

/** 插入 @param list 單鏈表 @param x 要插入的數據元素 @param i 要插入的位置 */
void insertLinkList(LinkList list, DataType x, int i) {
    
    LinkList s, p;
    
    if (i == 1) {
        p = list;
    } else {
        // 找到第 i-1 個數據元素結點
        p = getLinkList(list, i - 1);
    }
    
    if (p == NULL) {
        // 若是不存在, 返回錯誤信息
        printf("找不到插入的位置");
    } else{
        // 生成新結點並初始化
        s = malloc(sizeof(Node));
        s->data = x;
        
        // 新結點的指針域指向第i個元素
        s->next = p->next;
        
        // 第 i-1 個結點的指針域指向新結點
        p->next = s;
    }
}
複製代碼

刪除

在單鏈表中刪除第i個結點的基本操做爲: 找到線性表中第i-1個結點, 修改其指向直接後繼的指針

/** 刪除 @param list 單鏈表 @param i 要刪除結點的位置 */
void deleteLinkList(LinkList list, int i) {
    LinkList p;
    
    // 找到待刪結點的直接前驅
    if (i == 1) {
        p = list;
    } else {
        p = getLinkList(list, i-1);
    }
    
    // 若直接前驅存在, 且待刪結點存在
    if (p != NULL && p->next != NULL) {
        
        // q指向待刪結點
        LinkList q = p->next;
        
        // 移出待刪結點
        p->next = q->next;
        
        // 釋放已經移出的結點
        free(q);
    } else {
        printf("要不到要刪除的結點");
    }
}
複製代碼

求表長

在單鏈表存儲結構中, 線性表的長度等於單鏈表所含結點的個數(不含頭結點)

/** 求表的長度 @param list 單鏈表 @return 表的長度 */
int LengthLinkList(LinkList list) {
    
    // 用於計數
    int j = 0;
    
    // p指向頭指針
    LinkList p = list;
    
    // 當下一個結點不爲空時, 計數加1, p指向下一個結點
    while (p->next != NULL) {
        j++;
        p = p->next;
    }
    
    // 返回表的長度
    return j;
}
複製代碼

讀表元素

/** 讀表元素 @param list 單鏈表 @param i 要讀取的位置 @return 若是有返回讀取到的元素, 沒有則返回NULL */
LinkList getLinkList(LinkList list, int i) {
    
    // 用於計數
    int j = 1;
    
    // p指向首結點
    LinkList p = list->next;
    while (j < i && p != NULL) {
        j++;
        p = p->next;
    }
    
    // 若是j等於i, 則p指向的結點爲要找的結點
    if (j == i) {
        return p;
    } else { // 不然沒有要找的結點
        return NULL;
    }
}
複製代碼

定位

定位運算是給定表元素的值, 找出這個元素的位置

/** 定位 @param list 單鏈表 @param name 要查找的結點的名字 @return 若是查找到了, 則返回結點的位置, 若是沒有, 則返回0 */
int locateLinkList(LinkList list, char name[]) {
    int j = 0;
    
    // p指向首結點
    LinkList p = list->next;
    
    // 查找結點
    while (p != NULL && !(strcmp(p->data.name, name) == 0)) {
        j++;
        p = p->next;
    }
    
    // 若是有則返回序號, 沒有返回0
    if (p != NULL) {
        return j + 1;
    } else {
        return 0;
    }
}
複製代碼

其它鏈表

循環鏈表

普通鏈表的終端結點的next值爲NULL

循環鏈表的終端結點的next指向頭結點

特色

在循環鏈表中, 從任一結點出發都能掃描整個鏈表

雙向循環鏈表

在鏈表中設置兩個指針域, 一個指向後繼結點, 一個指向前驅結點, 這就是雙向鏈表

空雙向循環鏈表

非空雙向循環鏈表

雙向鏈表中結點的刪除

設p指向待刪結點, 刪除*p可這樣作

p -> prior -> next = p -> next; // p前驅結點的直接後繼指向p的後繼結點

p -> next -> prior = p -> prior; // p後繼結點的直接前驅指向p的前驅結點

free(p); // 釋放*p的空間

雙向鏈表中結點的插入

在p所指後面插入一個新的結點t, 插入可這樣作

t -> next = p -> next;

t -> prior = p;

p -> next -> prior = t;

p -> next = t;

順序實現與連接實現的比較

  1. 單鏈表的每一個包括數據域和指針域, 指針域須要佔用額外空間
  2. 從總體考慮, 順序表要預分配存儲空間, 若是預先分配得過大, 將形成浪費, 若分配得太小, 又將發生上溢;
  3. 單鏈表不須要預先分配空間, 只要內存內存空間沒有耗盡, 單鏈表中的結點個數就沒有限制.

順序表的完整代碼

#include <stdio.h>

// 定義一個結構體類型
typedef struct {
    char name[10];
    int age;
} DataType;

// 順序表的結構體定義
// 線性表的大小, 也就是最多存儲多少個數據元素
const int maxSize = 100;
typedef struct {
    
    // 所存放的數據類型爲int, 最多存放100個元素的數組
    DataType data[maxSize];
    
    // 當前所存放的數據元素的個數
    int length;
} SeqList;

/** 線性表的插入 @param pList 類型爲SeqList的結構體指針 @param x 要插入的類型爲DataType類型的數據元素 @param i 要插入的位置 */
void insertSeqList(SeqList * pList, DataType x, int i) {
    // 判斷線性表是否已滿
    if (pList->length == maxSize) {
        printf("表已滿 \n");
        return;
    }
    
    // 檢查插入位置是否合法
    if (i < 1 || i > (pList->length) + 1) {
        printf("插入位置不合法 \n");
        return;
    }
    
    // 進行插入操做
    // 從表的最後一個位置依次進行後移
    for (int j = pList->length; j >= i; j--) {
        pList->data[j] = pList->data[j-1];
    }
    
    // 後移完成以後, 對空出的位置進行賦值
    pList->data[i - 1] = x;
    
    // 表的長度加1
    pList->length++;
}

/** 線性表的刪除 @param pList 類型爲SeqList結構體的指針 @param i 要刪除的位置 */
void deleteSeqList(SeqList * pList, int i) {
    
    // 檢查刪除位置是否合法
    if (i < 1 || i > pList -> length) {
        printf("刪除位置不合法");
        return;
    }
    
    // 進行刪除操做, 依次左移
    for (int j = i; j < pList->length; j++) {
        pList->data[j - 1] = pList->data[j];
    }
    
    // 表的長度減1
    pList->length--;
}

/** 線性表的定位 @param pList 類型爲SeqList結構體的指針 @param name 要查找的姓名 @return 查找到的元素的位置 */
int locateSeqList(SeqList * pList, char name[]) {
    int i = 0;
    
    // 在順序表中查找值爲name的結點
    while (i < pList->length && pList->data[i].name == name) {
        i++;
    }
    
    // 若找到值爲name的結點, 則返回結點的序號
    if (i < pList->length) {
        return i + 1;
    } else { // 未找到值爲name的結點, 返回0
        return 0;
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    
    // 建立一個結構體實例, 並進行初始化
    DataType dt1 = {"alex", 18};
    DataType dt2 = {"kevin", 16};
    DataType dt3 = {"jack", 21};

    // 建立一個空的線性表
    SeqList list = {};
    
    // 建立一個指針指向這個線性表
    SeqList * pList = &list;
    
    printf("插入前表的長度: %d \n", list.length);
    
    // 插入操做
    insertSeqList(pList, dt1, 1);
    insertSeqList(pList, dt2, 1);
    insertSeqList(pList, dt3, 1);
    
    printf("插入後表的長度: %d \n", list.length);
    
    // 刪除操做
    deleteSeqList(pList, 2);
    
    printf("插入後表的長度: %d \n", list.length);
    
    // 定位
    int loc = locateSeqList(pList, "alex");
    printf("alex在第%d位 \n", loc);
    
    return 0;
}
複製代碼

單鏈表的完整代碼

#include <stdio.h>
#include <stdlib.h>

// 聲明一個結構體類型
typedef struct {
    char name[12];
    int age;
} DataType;


// 聲明一個單鏈表類型
typedef struct node {
    DataType data; // 數據域
    struct node * next; // 指針域
} Node, * LinkList;


/** 初始化一個單鏈表 @return 初始化好的單鏈表 */
LinkList initiateLinkList() {
    
    // 頭指針
    LinkList head;
    
    // 動態建立一個結點, 也就是頭結點, 而後讓頭指針指向這個結點
    head = malloc(sizeof(Node));
    
    // 頭結點的指針域爲NULL
    head->next = NULL;
    
    // 返回這個單鏈表
    return head;
}


/** 求表的長度 @param list 單鏈表 @return 表的長度 */
int LengthLinkList(LinkList list) {
    
    // 用於計數
    int j = 0;
    
    // p指向頭指針
    LinkList p = list;
    
    // 當下一個結點不爲空時, 計數加1, p指向下一個結點
    while (p->next != NULL) {
        j++;
        p = p->next;
    }
    
    // 返回表的長度
    return j;
}


/** 讀表元素 @param list 單鏈表 @param i 要讀取的位置 @return 若是有返回讀取到的元素, 沒有則返回NULL */
LinkList getLinkList(LinkList list, int i) {
    
    // 用於計數
    int j = 1;
    
    // p指向首結點
    LinkList p = list->next;
    while (j < i && p != NULL) {
        j++;
        p = p->next;
    }
    
    // 若是j等於i, 則p指向的結點爲要找的結點
    if (j == i) {
        return p;
    } else { // 不然沒有要找的結點
        return NULL;
    }
}


/** 插入 @param list 單鏈表 @param x 要插入的數據元素 @param i 要插入的位置 */
void insertLinkList(LinkList list, DataType x, int i) {
    
    LinkList s, p;
    
    if (i == 1) {
        p = list;
    } else {
        // 找到第 i-1 個數據元素結點
        p = getLinkList(list, i - 1);
    }
    
    if (p == NULL) {
        // 若是不存在, 返回錯誤信息
        printf("找不到插入的位置");
    } else{
        // 生成新結點並初始化
        s = malloc(sizeof(Node));
        s->data = x;
        
        // 新結點的指針域指向第i個元素
        s->next = p->next;
        
        // 第 i-1 個結點的指針域指向新結點
        p->next = s;
    }
}


/** 刪除 @param list 單鏈表 @param i 要刪除結點的位置 */
void deleteLinkList(LinkList list, int i) {
    LinkList p;
    
    // 找到待刪結點的直接前驅
    if (i == 1) {
        p = list;
    } else {
        p = getLinkList(list, i-1);
    }
    
    // 若直接前驅存在, 且待刪結點存在
    if (p != NULL && p->next != NULL) {
        
        // q指向待刪結點
        LinkList q = p->next;
        
        // 移出待刪結點
        p->next = q->next;
        
        // 釋放已經移出的結點
        free(q);
    } else {
        printf("要不到要刪除的結點");
    }
}


/** 定位 @param list 單鏈表 @param name 要查找的結點的名字 @return 若是查找到了, 則返回結點的位置, 若是沒有, 則返回0 */
int locateLinkList(LinkList list, char name[]) {
    int j = 0;
    
    // p指向首結點
    LinkList p = list->next;
    
    // 查找結點
    while (p != NULL && !(strcmp(p->data.name, name) == 0)) {
        j++;
        p = p->next;
    }
    
    // 若是有則返回序號, 沒有返回0
    if (p != NULL) {
        return j + 1;
    } else {
        return 0;
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    
    Node list = {};
    LinkList pList = &list;
    
    DataType dt1 = {"jack", 18};
    DataType dt2 = {"kevin", 20};
    
    // 插入元素
    insertLinkList(pList, dt1, 1);
    insertLinkList(pList, dt2, 1);
    
    // 讀表元素
    LinkList p = getLinkList(pList, 2);
    printf("%s \n", p->data.name);
    
    // 刪除元素
// deleteLinkList(pList, 2);
// deleteLinkList(pList, 1);
    
    // 讀表長
    int length = LengthLinkList(pList);
    printf("表的長度爲: %d \n", length);
    
    // 定位
    int loc = locateLinkList(pList, "kevin");
    printf("在第%d位 \n", loc);
    return 0;
}
複製代碼
相關文章
相關標籤/搜索