App下載

什么是線性表?數(shù)據結構之線性表講解!

草莓夾餅干 2022-01-18 11:18:12 瀏覽數(shù) (5511)
反饋

相信很多學習C語言、Java等編程語言的小伙伴們在掌握了基礎語法后就了解到了數(shù)據結構與算法,這兩個學科熬禿了多少程序員的頭。數(shù)據結構和算法的關系是依賴的,實現(xiàn)算法需要一定的數(shù)據結構,數(shù)據結構有很多種類,其中最簡單的一種就是線性表,而線性表中又分為順序表和鏈式表(簡稱鏈表),我們就來介紹一下線性表的這兩種表。

基礎數(shù)據類型

我們知道,每一門語言都有一些基礎的數(shù)據類型,比如int,float,char,這些數(shù)據類型就像一個一個的點。但是我們在實際使用的時候會把一些有關聯(lián)的點組合起來使用,這就是有結構的數(shù)據(比如將一個一個的數(shù)據存放在一起,這就是一個集合(枚舉類型))。

線性表

數(shù)據結構不僅描述了數(shù)據的格式,還描述了數(shù)據與數(shù)據間的關系,最常見的就是數(shù)據與數(shù)據之間一一聯(lián)系,前一個數(shù)據和后一個數(shù)據相連,這種數(shù)據結構最后會像線一樣連成一串,這種數(shù)據結構也因此得名為線性表。

線性表的實現(xiàn)有兩種形式,這兩種實現(xiàn)方式的不同主要是內存造成的:

順序表

實現(xiàn)線性表的最簡單的方式,就是在一片連續(xù)的內存中按照順序填充數(shù)據,這樣每個數(shù)據都會像排隊一樣在內存空間里有一定的位置。要訪問數(shù)據的前一個數(shù)據,只要在內存中相應偏移一定的量(通常是一個數(shù)據的長度),就能訪問到相應的數(shù)據,訪問后一個數(shù)據也可以使用相同的方式。

實際上在看到連續(xù)的內存這個字眼,小伙伴們應該馬上就想到了吧?沒錯,就是數(shù)組(在python中沒有數(shù)組,但有隊列和元組,在此處對應的是元組,下文會有所介紹)

 null arr0=W arr1=3  arr2=C  arr3=S  arr4=C  arr5=H  arr6=o  arr7=o  arr8=l 
 null null  null  null  null  null  null  null  null  null 

如上所示,這就是一個典型的順序表。

順序表的問題

前文中我們提到,創(chuàng)建順序表需要開辟一塊固定的空間,通常這個空間開辟后就無法修改其容量,也就是所這個順序表初始化的時候只能存放10個數(shù)據,就不能存放超過十個數(shù)據。這樣的使用會存在這樣的問題:我怎么知道我需要多大的空間,換言之,在不知道需要多大空間的時候,使用順序表就要根據經驗來判斷了,如果預先開辟過大的空間,就會有空間浪費的問題。另外,順序表的數(shù)據操作也有一定的資源浪費,修改數(shù)據上邏輯是正常的,我只要到固定的位置改動數(shù)據即可,刪除和添加最后一個數(shù)據也是正常的,還是到最后的位置處理數(shù)據即可,那么我要在順序表中間或者開頭插入一個數(shù)據呢?這個時候我就要在要插入的位置開始,將所有的數(shù)據后移一位,才能將新數(shù)據填充進去,刪除也有同樣的情況,要將要刪除的

此外還有另一個問題:如果我本身沒有這么多空間呢?內存的使用并不是有規(guī)律的,開辟一塊固定寬度的空間很多時候只是一種奢求,如何在支零破碎的內存中利用好每個空隙,這就是接下來的主角——鏈式表的優(yōu)勢了。

鏈式表

與順序表不同,順序表是將一堆數(shù)據捆在一起,使用物理相鄰的方式來確定他們的關系。而鏈式表這是數(shù)據與數(shù)據之間各自在其內存空間,不過數(shù)據中保存著訪問下一個(或者上一個)數(shù)據的方法。如何通俗的理解這個方法呢?假如一群朋友去看電影,但是沒有辦法買到連坐的位置,想要知道某個人的位置,你可以問旁邊的你的朋友,因為你知道你旁邊的你的朋友在哪,而你的朋友又可以問你的另一個朋友,知道找到那個朋友。如下所示:

 arr1 =W
下一個目標是
第三格
 null arr2 =3
下一個目標是
第五格 
 null arr3 =C 
下一個目標是
第六格
arr4=/0
這里是鏈表尾部 
 null

在C語言中使用指針來保存下一個(或上一個)數(shù)據的數(shù)據地址,在其他語言中使用引用來保存下一個(或者上一個)的數(shù)據(因為是引用,所以實際上也是保存了一個數(shù)據地址),如下所示:


一個node就是一個數(shù)據,也就是一個數(shù)據節(jié)點,每個節(jié)點由數(shù)據域和指針域構成,數(shù)據域用來存放數(shù)據,而指針域用來指定下一個目標節(jié)點。

由于鏈表的這種特性,要在中間插入一個數(shù)據,只需要新建一個數(shù)據節(jié)點,然后將前一個數(shù)據的指針域指向這個新數(shù)據,然后將這個新數(shù)據指向后一個指針域即可。刪除也很簡單,我們只需要將前一個數(shù)據的指針域指向下一個數(shù)據,然后刪除這個數(shù)據即可(在c語言中線性表需要自行實現(xiàn),或者使用相應的庫,而在python中列表具有相應的功能,但列表并不只是單純的鏈式表,你可以看做他是鏈式表的升級版或者超集)。

要想知道一個鏈表有多少個數(shù)據節(jié)點,只有將所有的數(shù)據遍歷過才能知道,當然,你可以選擇用一個數(shù)據節(jié)點來保存這個鏈表的長度,但是你需要訪問最后一個節(jié)點,假如這個鏈表的長度為十萬個,那么就需要執(zhí)行十萬次數(shù)據讀取才能得到這個節(jié)點的數(shù)據,而順序表只需要一次(他可以直接通過內存偏移得到相應的數(shù)據)

鏈表的C語言實現(xiàn):單鏈表

#include <stdio.h>
#include <stdlib.h>
typedef struct Link{
    int  elem;
    struct Link *next;
}link;
link * initLink();
//鏈表插入的函數(shù),p是鏈表,elem是插入的結點的數(shù)據域,add是插入的位置
link * insertElem(link * p,int elem,int add);
//刪除結點的函數(shù),p代表操作鏈表,add代表刪除節(jié)點的位置
link * delElem(link * p,int add);
//查找結點的函數(shù),elem為目標結點的數(shù)據域的值
int selectElem(link * p,int elem);
//更新結點的函數(shù),newElem為新的數(shù)據域的值
link *amendElem(link * p,int add,int newElem);
void display(link *p);
int main() {
    //初始化鏈表(1,2,3,4)
    printf("初始化鏈表為:\n");
    link *p=initLink();
    display(p);
   
    printf("在第4的位置插入元素5:\n");
    p=insertElem(p, 5, 4);
    display(p);
   
    printf("刪除元素3:\n");
    p=delElem(p, 3);
    display(p);
   
    printf("查找元素2的位置為:\n");
    int address=selectElem(p, 2);
    if (address==-1) {
        printf("沒有該元素");
    }else{
        printf("元素2的位置為:%d\n",address);
    }
    printf("更改第3的位置的數(shù)據為7:\n");
    p=amendElem(p, 3, 7);
    display(p);
   
    return 0;
}
link * initLink(){
    link * p=(link*)malloc(sizeof(link));//創(chuàng)建一個頭結點
    link * temp=p;//聲明一個指針指向頭結點,用于遍歷鏈表
    //生成鏈表
    for (int i=1; i<5; i++) {
        link *a=(link*)malloc(sizeof(link));
        a->elem=i;
        a->next=NULL;
        temp->next=a;
        temp=temp->next;
    }
    return p;
}
link * insertElem(link * p,int elem,int add){
    link * temp=p;//創(chuàng)建臨時結點temp
    //首先找到要插入位置的上一個結點
    for (int i=1; i<add; i++) {
        if (temp==NULL) {
            printf("插入位置無效\n");
            return p;
        }
        temp=temp->next;
    }
    //創(chuàng)建插入結點c
    link * c=(link*)malloc(sizeof(link));
    c->elem=elem;
    //向鏈表中插入結點
    c->next=temp->next;
    temp->next=c;
    return  p;
}
link * delElem(link * p,int add){
    link * temp=p;
    //遍歷到被刪除結點的上一個結點
    for (int i=1; i<add; i++) {
        temp=temp->next;
    }
    link * del=temp->next;//單獨設置一個指針指向被刪除結點,以防丟失
    temp->next=temp->next->next;//刪除某個結點的方法就是更改前一個結點的指針域
    free(del);//手動釋放該結點,防止內存泄漏
    return p;
}
int selectElem(link * p,int elem){
    link * t=p;
    int i=1;
    while (t->next) {
        t=t->next;
        if (t->elem==elem) {
            return i;
        }
        i++;
    }
    return -1;
}
link *amendElem(link * p,int add,int newElem){
    link * temp=p;
    temp=temp->next;//tamp指向首元結點
    //temp指向被刪除結點
    for (int i=1; i<add; i++) {
        temp=temp->next;
    }
    temp->elem=newElem;
    return p;
}
void display(link *p){
    link* temp=p;//將temp指針重新指向頭結點
    //只要temp指針指向的結點的next不是Null,就執(zhí)行輸出語句。
    while (temp->next) {
        temp=temp->next;
        printf("%d",temp->elem);
    }
    printf("\n");
}

回到上文的問題

為什么在python中順序表對應的是元組而不是列表?原因很簡單,python的列表是可以從中間插入數(shù)據的,而這是鏈式表的特性,元組不能實現(xiàn)這個功能。

小結:順序表和鏈式表的優(yōu)缺點比較

   順序表 鏈式表 
 內存使用 連續(xù)的一塊空間,數(shù)據相鄰,通過物理相鄰的方式實現(xiàn)數(shù)據之間的聯(lián)系 。
因此不能動態(tài)調整長度
(內存使用率較低)
每個數(shù)據節(jié)點不一定相鄰,通過指針的方式實現(xiàn)數(shù)據之間的聯(lián)系
因此可以調整動態(tài)長度 
 刪除 可以刪除最后一個數(shù)據,刪除其他位置的數(shù)據的時候需要將相應的數(shù)據前移
(刪除成本高)
 數(shù)據可以隨意刪除,只需要將前一個節(jié)點的指針指向后一個節(jié)點
 插入 可以在順序表最后添加一個新的數(shù)據,在其他位置插入數(shù)據的時候需要將相應的數(shù)據后移再插入
(插入成本高)
數(shù)據可以隨意插入,只需要將前一個節(jié)點的指針指向新的節(jié)點,再將新的節(jié)點的指針指向下一個節(jié)點 
 元素查詢 索引與內存偏移正相關,只需要知道索引就可以得到相應的內存地址,只需要執(zhí)行一次數(shù)據訪問就可以得到數(shù)據  需要一個節(jié)點一個節(jié)點的查找才能得到數(shù)據,當節(jié)點在末尾的時候需要遍歷整個鏈表才能得到數(shù)據。
(查詢成本高) 


1 人點贊