數據結構-線性結構

線性表

線性表是最簡單最多見的數據結構,屬於邏輯結構;node

線性表有兩種實現方式(存儲方式),分別是順序實現和連接實現;算法

定義:

線性表是由n(>=0)個數據元素組成的有限序列,數據元素的個數n定義爲表的長度;數組

術語:

  • 前驅, 後繼, 直接前驅, 直接後繼, 長度, 空表

案例:數據結構

線性表用L表示,一個非空線性表可記爲L = (a1,a2,..an);測試

a1後面的稱爲a1的後繼spa

an前面的稱爲an的前驅設計

a1爲起始節點,an爲終端節點,任意相鄰的兩個元素,如a1和a2,a1是a2的直接前驅,a2是a1的直接後繼;3d

線性表中元素個數即表的長度,此處爲n;指針

表中沒有任何元素時,稱爲空表code

除了首節點和尾節點以外,每一個節點都有且只有一個直接前驅和直接後繼,首節點沒有前驅,尾節點沒有後繼;

節點之間的關係屬於一對一;

線性表的基本運算

  • 初始化

​ Initiate(L) 創建一個空表L(),L不包含數據元素

  • 求表長度

​ Length(L) 返回線性表的長度

  • 取表元素

​ Get(L,i) 返回線性表的第i個元素,i不知足1<=i<=Length(L)時,返回特殊值;

  • 定位

​ Locate(L,x)查找x在L中的節點序號,如有多個匹配的返回第一個,若沒有匹配的返回0;

  • 插入

​ Insert(L,x,i)將x插入到L的第i個元素的前面(其餘元素日後挪),參數i取值範圍爲1<=i<=Length(L)+1;運算結束後表長度+1;

  • 刪除

​ Delete(L,i)刪除表L中的第i個元素,i有效範圍1<=i<=Length(L);操做結束後表長度-1

強調:上述的第i個指的是元素的序號從1開始,而不是下標從0開始;

另外:插入操做要保證操做後數據仍是一個接着一個的不能出現空缺;

線性表的順序存儲實現

線性表是一種邏輯結構,能夠經過順序存儲結構來實現,即:

​ 將表中的節點一次存放在計算機內存中一組連續的存儲單元中,數據元素在線性表中的鄰接關係決定了它們在存儲空間中的存儲位置;換句話說邏輯結構中相鄰的兩個節點的實際存儲位置也相鄰;

用順序存儲結構實現的線性表也稱之爲爲順序表,通常採用數組來實現;

圖示:

大小與長度:

線性表的大小:指的是最大能存儲的元素個數

線性表的長度:指的是當前已存儲的個數

示例:

c語言實現:

#include <stdio.h>

//初始化操做:
const MAX_SIZE = 5;//最大長度
typedef struct list {
    int data[MAX_SIZE];//數組
    int length;//當前數據長度
};

//獲取targert在表中的位置
int locate(struct list *l,int target){
    for (int i = 0;i < l->length;i++){
        if (target == l->data[i]){
            return i + 1;
        }
    }
    return 0;
}
//獲取第loc個元素
int get(struct list *l,int loc){
    if (loc < 1 || loc > l->length){
        printf("error:位置超出範圍\n");
        return -1;
    }else{
        return l->data[loc-1];
    }
}

//插入一個元素到第loc個位置上
void insert(struct list *l,int data,int location){
    if (l->length == MAX_SIZE){
        printf("errolr:表容量已滿\n");
        return;
    }
    if (location < 1 || location > l->length+1){
        printf("error:位置超出範圍\n");
        return;
    }
    //目標位置後面的內容以此日後挪
    for (int i = l->length; i >= location; i--) {
        l->data[i] = l->data[i-1];
    }
    //在目標位置放入新的數據
    l->data[location-1] = data;
    l->length+=1;//長度加1
}
//刪除第loc個元素,從目標位置日後的元素一次向前移動
void delete(struct list *l,int loc){
    if (loc < 1|| loc > l->length){
        printf("error:位置超出範圍\n");
        return;
    }
   //目標位置及後面的全部元素所有向後移動
    for (;loc < l->length; ++loc) {
        l->data[loc-1] = l->data[loc];
    }
    l->length-=1;
}

//打印全部元素 測試用
void show(struct list l){
    for (int i = 0; i < l.length; ++i) {
        printf("%d\n",l.data[i]);
    }
}
//測試
int main() {
    struct list alist = {};
    insert(&alist,100,alist.length+1);
    insert(&alist,200,alist.length+1);
    insert(&alist,300,alist.length+1);
    insert(&alist,400,alist.length+1);
    delete(&alist,1);
    printf("%d\n",alist.length);
    show(alist);
    printf("%d\n",get(&alist,4));
    printf("%d\n", locate(&alist,300));
    printf("%d\n", get(&alist,1));
    return 0;
}

插入算法分析:

假設線性表中含有n個元素,

在插入元素時,有n+1個位置能夠插入,由於要保證數據是連續的

每一個位置插入數據的機率是: 1/(n+1)

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

算法時間複雜度爲:O(n)

刪除算法分析:

假設線性表中含有n個元素,

在刪除元素時,有n個位置能夠刪除

每一個位置插入數據的機率是: 1/n

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

算法時間複雜度爲:O(n)

插入與刪除的不足

順序表在進行插入和刪除操做時,平均要移動大約一半的數據元素,當存儲的數據量很是大的時候,這一點須要特別注意;

簡單的說,順序表在插入和刪除時的效率是不夠好的;特別在數據量大的狀況下;

順序表總結:

1.順序表是一維數組實現的線性表

2.邏輯上相鄰的元素,在存儲結構中也是相鄰的

3.順序表可實現隨機讀取

優缺點:

優勢:

  • 無需爲了表示元素直接的邏輯關係而增長額外的存儲空間
  • 可方便的隨機存取表中的任一節點

缺點:

  • 插入和刪除運算不方便,須要移動大量的節點
  • 順序表要求佔用連續的存儲空間,必須預先分配內存,所以當表中長度變化較大時,難以肯定合適的存儲空間大小;

順序表節點存儲地址計算:

設第i個節點的存儲地址爲x

設順序表起始地址爲loc,每一個數據元素佔L個存儲單位

計算公式爲:x = loc + L * (i-1)

如 loc = 100 i = 5 L = 4 則 x = 116

線性表的連接存儲實現

線性表也可經過連接存儲方式來實現,用連接存儲方式實現的線性表也稱爲鏈表 Link List

鏈式存儲結構:

1.可用任意一組存儲單元來存儲數據

2.鏈表中節點的邏輯次序與物理次序不必定相同

3.每一個節點必須存儲其後繼節點的地址信息(指針)

圖示:

單鏈表

單鏈表指的是隻能沿一個方向查找數據的鏈表,如上圖

每一個節點由兩個部分(也稱爲域)組成

  • data域 存放節點值得數據域
  • next域 存放節點的直接後繼的地址的指針域(也稱爲鏈域)

節點結構:

每一個節點只知道本身後面一個節點殊不知道本身前面的節點因此稱爲單鏈表

圖示:
image-20200214191940070

帶有head節點的單鏈表:
image-20200214192038152

單鏈表的第一個節點一般不存儲數據,稱爲頭指針,使用頭指針來存儲該節點的地址信息,之因此這麼設計是爲了方便運算;

單鏈表特色:

  • 其實節點也稱爲首節點,沒有前驅,因此頭指針要指向該節點,以保證可以訪問到起始節點;
  • 頭指針能夠惟一肯定一個鏈表,單鏈表可使用頭指針的名稱來命名;
  • 終端節點也稱尾節點,沒有後繼節點,因此終端節點的next域爲NULL;
  • 除頭結點以外的幾點稱爲表結點
  • 爲方便運算,頭結點中不存儲數據

單鏈表數據結構定義

//數據結構定義
typedef struct node {
    struct node *next;
    int data,length;
} Node, *LinkList;
/*
 * typedef 是用來取別名的
 * Node 是struct node 的別名
 * *LinkList 是 struct node *的別名
 * 後續使用就不用在寫struct關鍵字了
 */

運算:

初始化

一個空鏈表有一個頭指針和一個頭結點構成

假設已定義指針變量L,使L指向一個頭結點,並使頭結點的next爲NULL

//時間複雜度  :O(1)
LinkList initialLinkList() {
    // 定義鏈表的頭結點
    LinkList head;
    //申請空間
    head = malloc(sizeof(struct node));
    //使頭結點指向NULL
    head->next = NULL;
    return head;
}

求表長

從頭指針開始遍歷每一個節點知道某個節點next爲NULL爲止,next不爲空則個數len+1;

//求表長  時間複雜度  :O(n)
int length(LinkList list){
    int len = 0;
    Node *c = list->next;
    while(c != NULL){
        len+=1;
        c = c->next;
    }
    return len;
}

讀表元素

給定一個位置n,獲取該位置的節點

​ 遍歷鏈表,過程當中若某節點next爲NULL或已遍歷個數index=n則結束循環

//從鏈表中獲取第position個位置的節點  時間複雜度  :O(n)
Node *get(LinkList list, int position) {
    Node *current;
    int index = 1;
    current = list->next;
    //若是下面還有值而且尚未到達指定的位置就繼續遍歷   要和查找元素區別開 這就是一直日後遍歷直到位置匹配就好了
    while (current != NULL && index < position) {
        current = current->next;
        index += 1;
    }
    if (index == position) {
        return current;
    }
    return NULL;
}

定位

對給定表元素的值,找出這個元素的位置

​ 遍歷鏈表,若某節點數據域與要查找的元素data相等則返回當前遍歷的次數index

//求表head中第一個值等於x的結點的序號(從1開始),若不存在這種結點,返回結果爲0    時間複雜度  :O(n)
int locate(LinkList list,int data){
    int index = 1;
    Node *c;
    c = list->next;
    while (c != NULL){
        if (c->data == data){
            return index;
        }
        index+=1;
        c = c->next;
    }
    return 0;
}

插入

在表的第i個數據元素結點以前插入一個以x爲值的新結點new

​ 獲取第i的節點的直接前驅節點pre(若存在),使new.next = pre.next;pre.next = new;

//在表head的第i個數據元素結點以前插入一個以x爲值的新結點  時間複雜度  :O(n)
void insert(LinkList list, int position, int data) {
    Node *pre, *new;
    if (position == 1) {
        //若插入位置爲1 則表示要插入到表的最前面 即head的後面
        pre = list;
    } else {
        //pre表示目標位置的前一個元素  因此-1
        pre = get(list, position - 1);
        if (pre == NULL) {
            printf("error:插入的位置超出範圍");
            exit(0);
        }
    }
    new = malloc(sizeof(Node));
    new->data = data;
    new->next = pre->next;
    pre->next = new;
    list->length += 1;

}

刪除

刪除給定位置的節點

​ 獲取目標節點target的直接前驅節點pre(若pre與目標都有效),pre.next = target.next; free(target);

//刪除鏈表中第position個位置的節點   時間複雜度  :O(n)
void delete(LinkList list,int position){
    //獲取要刪除節點的直接前驅
    Node *pre;
    if (position == 1){ //如要刪除的節點是第一個那直接前驅就是頭結點
        pre = list;
    }else{
        pre = get(list,position-1);
    }
    ////若是目標和前驅都存在則執行刪除
    if (pre != NULL && pre->next != NULL){
        Node *target = pre->next; //要刪除的目標節點
       //直接前驅的next指向目標的直接後繼的next
        pre->next = target->next;
        free(target);
        printf("info: %d被刪除\n",target->data);
        list->length -= 1;
    }else{
        printf("error:刪除的位置不正確!");
        exit(1);
    }
}

建立具有指定數據節點的鏈表

//效率比較差算法 時間複雜度  :O(n^2)
LinkList createLinkList1(){
    LinkList list = initialLinkList();
    int a;//輸入的數據
    int index = 1;  //記錄當前位置
    scanf("%d",&a);
    while (a != -1){    // O(n)
        insert(list,index++,a); // O(n^2)     每次都要從頭遍歷鏈表
        scanf("%d",&a);
    }
    return list;
}
//尾插算法 記錄尾節點 從而避免遍歷  時間複雜度  :O(n)
LinkList createLinkList2(){
    LinkList list = initialLinkList();
    int a;//輸入的數據
    Node *tail = list;//當前的尾部節點
    scanf("%d",&a);
    while (a != -1){    // O(n)
        Node * newNode = malloc(sizeof(Node)); //新節點
        newNode->next = NULL;
        newNode->data = a;

        tail->next = newNode;//尾部節點的next指向新節點
        tail = newNode;//新節點做爲尾部節點
        scanf("%d",&a);
    }
    return list;
}
//頭插算法 每次插到head的後面,不用遍歷可是順序與插入時相反  時間複雜度  :O(n)
LinkList createLinkList3(){
    LinkList list = initialLinkList();
    int a;//輸入的數據
    Node * head = list;
    scanf("%d",&a);
    while (a != -1){    // O(n)
        Node * newNode = malloc(sizeof(Node)); //新節點
        newNode->next = NULL;
        newNode->data = a;

        newNode->next = head->next;//將本來head的next 交給新節點;
        head->next = newNode;//在把新節點做爲head的next;
        scanf("%d",&a);
    }
    return list;
}

優缺點

優勢:

  • 在非終端節點插入刪除時無需移動其餘元素
  • 無需預分配空間,大小沒有限制(內存夠的狀況)

缺點:

  • 沒法隨機存取
  • 讀取數據慢

鏈表與順序表的對比:

操做 順序表 鏈表
讀表元 O(1) O(n)
定位 O(n) O(n)
插入 O(n) O(n)
刪除 O(n) O(n)
相關文章
相關標籤/搜索