數(shù)據(jù)結(jié)構(gòu)——線性表

前言

編程語言赡勘,相當(dāng)于各個門派的武功嫂便;數(shù)據(jù)結(jié)構(gòu),則相當(dāng)于內(nèi)功闸与。習(xí)得一招一式毙替,稱霸武林,內(nèi)功修煉必不可少践樱!
這篇文章系本人原創(chuàng)厂画,謝絕轉(zhuǎn)載。
以下將介紹數(shù)據(jù)結(jié)構(gòu)中最基礎(chǔ)也是最重要的一種結(jié)構(gòu)——線性表拷邢。
線性表是最基本的數(shù)據(jù)結(jié)構(gòu)袱院,有兩種存儲方式:順序結(jié)構(gòu)、鏈表結(jié)構(gòu)
注意:同一個線性表中的元素類型必須相同。

一忽洛、順序結(jié)構(gòu)

順序表中腻惠,相鄰元素之間的物理地址是連續(xù)的,也就是說欲虚,計算機(jī)會用一組連續(xù)的內(nèi)存單元存放表中各個元素集灌。從下面的代碼中可以看出數(shù)組cs的指針地址指向第一個元素。

    char cs[] = {'a','b','c'};
    printf("cs = %p \n",cs);//打印數(shù)組地址
    
    for (int i = 0; i < 3; i++) {
        printf("cs[%d] = %p\n", i, &cs[i]);//打印元素地址
    }
    /*
     打印結(jié)果
     cs = 0x7ffee82dc08d
     cs[0] = 0x7ffee82dc08d
     cs[1] = 0x7ffee82dc08e
     cs[2] = 0x7ffee82dc08f
     */

下面是一道經(jīng)典的數(shù)組指針的面試題

int a[5] = { 1, 2, 3, 4, 5 };
int *p = (int *)(&a + 1);
printf("%d,%d \n", *(a + 1), *(p - 1));
//打印結(jié)果:2复哆,5

由于數(shù)組中的元素的地址是連續(xù)的欣喧,又因?yàn)閕nt型占4個字節(jié)。*是指針運(yùn)算符梯找,&是取址運(yùn)算符唆阿,它倆是互為相反的。具體可以看*運(yùn)算符與&運(yùn)算符

分析步驟
1.打印數(shù)組中元素地址
for (int i=0; i < 5; i++) {
     printf("a[%d] = %p\n", i, &a[i]);
 }
/*
a[0] = 0x7ffee79d3080
a[1] = 0x7ffee79d3084
a[2] = 0x7ffee79d3088
a[3] = 0x7ffee79d308c
a[4] = 0x7ffee79d3090
*/
2.&a取出a的地址锈锤,也就是第一個元素的地址驯鳖,然后+1,則p指向a數(shù)組最后一個元素的后面一個存儲空間牙咏,打印出p的指針為p = 0x7ffee79d3094臼隔,然后p-1,則相當(dāng)于向前移動4個字節(jié)妄壶,所以指向的是數(shù)組a中最后一個元素摔握,結(jié)果為5。a是數(shù)組名丁寄,也是首元素的地址氨淌,然后+1,則相當(dāng)于后移動4個字節(jié)伊磺,所以指向第二個元素盛正,結(jié)果為2。
1.插入刪除操作時屑埋,效率比較低

這是因?yàn)樵诓迦牒荔荨h除時,需要移動大量的數(shù)據(jù)元素摘能,比較耗時续崖。假設(shè)順序表中元素個數(shù)為n(不考慮順序表擴(kuò)容問題),將新元素插入最后一個位置团搞,則時間復(fù)雜度為O(1)严望;插入新的元素到第一個位置,則需要將之后的n個元素整體向后移動逻恐,時間復(fù)雜度為O(n)像吻,平均的時間復(fù)雜度為O((n+1)/2)峻黍,也就是平均需要移動一半的元素。刪除元素也是同理拨匆。

2.存取元素值效率比較高

如果已知元素的下標(biāo)花沉,則可以直接取出或修改元素的值呈队,時間復(fù)雜度為O(1)佑吝。如果已知元素值為x绣否,查找出元素的下標(biāo),則需要從第一個元開始分別與x比較洪鸭,如果該元素剛好在第一個元素,則時間復(fù)雜度為O(1)仑扑,如果在最后一個元素則為O(n)览爵,平均為O((n+1)/2)。
總結(jié):順序表在插入刪除時镇饮,需要大量移動數(shù)據(jù)元素蜓竹,效率較低。由于是靜態(tài)存儲結(jié)構(gòu)储藐,需要確定數(shù)組的大小俱济,容量有限。適用于頻繁存取元素數(shù)據(jù)钙勃。
具體實(shí)現(xiàn)如下:

 #define MAXSIZE 10

typedef struct {
    int data[MAXSIZE];
    int last;//記錄數(shù)組中最后一個元素的位置蛛碌,默認(rèn)值為-1,表是空表
}SeqList;

//指定位置插入元素
int insert_seqList(SeqList *list,int i,int x){
    if(list->last == MAXSIZE-1){
        return -1;//表空間已滿辖源,不能插入
    }
    
    if(i < 0 || i > list->last+1){
        return -1;//插入位置不正確
    }else{
        //將i之后的元素整體向后移動
        for (int j = list->last; j>=i; j--) {
            list->data[j+1] = list->data[j];
        }
        list->data[i] = x;
        list->last++;
    }
    return 1;
}

//刪除指定位置的元素
void delete_seqList(SeqList *list,int i){
    if(list->last < 0){
        return;//空表
    }
    if(i<0 || i>list->last+1){
        return;//刪除的下標(biāo)越界
    }
    //i之后的元素整體向前移動
    for (int j=i; j<list->last; j++) {
        list->data[j] = list->data[j+1];
    }
    list->last--;
}
//根據(jù)數(shù)值查找出在表中的下標(biāo)
int location_seqlist(SeqList *list,int x){
    if(list->last < 0){
        return -1;//空表
    }
    for (int i=0; i<=list->last; i++) {
        if(list->data[i] == x){
            return i;
        }
    }
    return -1;
}
//打印數(shù)組
void print_list(SeqList *list){
    if(list->last < 0){
        printf("數(shù)組為空 \n");
    }else{
        printf("--------------數(shù)據(jù)個數(shù)為 %d--------------\n",list->last+1);
        for (int i=0; i<list->last+1; i++) {
            printf("數(shù)組第%d個 = %d \n",i,list->data[i]);
        }
    }
}

int main(int argc, char * argv[]) {
    NSLog(@"%s",__func__);
    
    SeqList list;
    //設(shè)置默認(rèn)值
    list.last = 2;
    list.data[0] = 1;
    list.data[1] = 2;
    list.data[2] = 3;
    
    //插入
    insert_seqList(&list, 0, 11);
    insert_seqList(&list, 0, 22);
    insert_seqList(&list, 0, 33);
    insert_seqList(&list, 0, 44);
    insert_seqList(&list, 0, 55);
    print_list(&list);
    
    //刪除
    delete_seqList(&list, 0);
    delete_seqList(&list, 5);
    print_list(&list);
    
    int i = location_seqlist(&list, 3);
    printf("數(shù)據(jù)下標(biāo) i = %d \n",i);
}

二蔚携、鏈表結(jié)構(gòu)

鏈表中元素的物理地址可以是不連續(xù)的。鏈表與順序表不同克饶,它是一種動態(tài)管理的存儲結(jié)構(gòu)酝蜒,鏈表中的每個節(jié)點(diǎn)占用的內(nèi)存空間不是預(yù)先分配的,而是運(yùn)行時系統(tǒng)根據(jù)需求生成的矾湃。
每個節(jié)點(diǎn)包含元素數(shù)值外亡脑,還包含下一個節(jié)點(diǎn)的地址,如果是雙向鏈表邀跃,還包含上一個節(jié)點(diǎn)的地址霉咨。

1.插入刪除操作,效率較高

由于鏈表在插入刪除時坞嘀,不需要大量移動數(shù)據(jù)元素躯护。又因?yàn)槭莿討B(tài)存儲結(jié)構(gòu),不需要預(yù)先定義大小丽涩,可根據(jù)需求申請或釋放節(jié)點(diǎn)棺滞。如果在某個節(jié)點(diǎn)p之后插入新的節(jié)點(diǎn)s裁蚁,則時間復(fù)雜度為O(1)。如果在某個節(jié)點(diǎn)p之前插入新的節(jié)點(diǎn)s继准,則需要從第一個節(jié)點(diǎn)開始遍歷找到新節(jié)點(diǎn)s的直接前驅(qū)節(jié)點(diǎn)p枉证,時間復(fù)雜度為O(n),有一種更好的實(shí)現(xiàn)方式移必,直接將新的節(jié)點(diǎn)s插入到節(jié)點(diǎn)p之后室谚,然后交換節(jié)點(diǎn)p于節(jié)點(diǎn)s的數(shù)值,這樣的操作時間復(fù)雜度為O(1)崔泵。

2.存取元素值效率比較低

不適用于直接存取操作秒赤,要取出表中任意一個元素必須從第一個元素開始向后查詢。如果查找的節(jié)點(diǎn)是第一個節(jié)點(diǎn)憎瘸,則時間復(fù)雜度為O(1)入篮;如果查找的節(jié)點(diǎn)是最后一個節(jié)點(diǎn),則時間復(fù)雜度為O(n)幌甘,平均操作時間復(fù)雜度為O((n+1)/2)潮售。
1.單向鏈表
對單向鏈表而言,只能從頭節(jié)點(diǎn)開始遍歷整個鏈表锅风。
單向鏈表的使用例子酥诽,計算兩個多項(xiàng)式的和

//頭文件
@interface WLPoly : NSObject

@property (nonatomic,assign) NSInteger coef;//系數(shù)
@property (nonatomic,assign) NSInteger exp;//指數(shù)

@property (nonatomic,strong) WLPoly *nextNode;

//兩個多項(xiàng)式相加
+(void)addPoly:(WLPoly *)poly1 poly2:(WLPoly *)poly2;
//打印多項(xiàng)式
+(void)printPoly:(WLPoly *)poly;

@end


//實(shí)現(xiàn)
//多項(xiàng)式的相加
+(void)addPoly:(WLPoly *)poly1 poly2:(WLPoly *)poly2{
    WLPoly *p = poly1.nextNode;//多項(xiàng)式1的相加指針變量
    WLPoly *q = poly2.nextNode;//多項(xiàng)式2的相加指針變量
    WLPoly *r = poly1;//
    WLPoly *pc = poly1;//
    
    while (p != nil && q != nil) {
        //多項(xiàng)式的指數(shù)相等,系數(shù)相加
        if(p.exp == q.exp){
            NSInteger sum = p.coef + q.coef;
            
            if(sum != 0){//系數(shù)和非零
                p.coef = sum;
                r = p;
            }else{//系數(shù)和為零
                r.nextNode = p.nextNode;
            }
            p = p.nextNode;
            q = q.nextNode;
            
        }else if(p.exp > q.exp){
            r.nextNode = p;
            r = p;
            p = p.nextNode;
        }else{
            r.nextNode = q;
            r = q;
            q = q.nextNode;
        }
    }
    if(p == nil){
        r.nextNode = q;
    }else{
        r.nextNode = p;
    }
    [self printPoly:pc];
}

+(void)printPoly:(WLPoly *)poly{
    if(poly == nil){
        return;
    }
    NSMutableString *string = [[NSMutableString alloc]initWithCapacity:10];
    WLPoly *p = poly.nextNode;
    while (p != nil) {
        
        NSString *s = [NSString stringWithFormat:@"%ldX%ld",p.coef,p.exp];
        if(p.coef < 0){
            s = [NSString stringWithFormat:@"%ldX%ld",-p.coef,p.exp];
            if(string.length > 0){
                [string appendString:@" - "];
            }
        }else{
            if(string.length > 0){
                [string appendString:@" + "];
            }
        }
        if(p.exp == 0){
            s= [NSString stringWithFormat:@"%ld",p.coef];
        }
        [string appendString:s];
        p = p.nextNode;
    }
    NSLog(@"最后結(jié)果:%@",string);
}
測試代碼
-(void)testPoly{
    WLPoly *polyA = [[WLPoly alloc]init];
    WLPoly *p1 = [self nextNodeWithCoef:6 exp:13];
    WLPoly *p2 = [self nextNodeWithCoef:2 exp:10];
    WLPoly *p3 = [self nextNodeWithCoef:-5 exp:4];
    WLPoly *p4 = [self nextNodeWithCoef:14 exp:0];
    polyA.nextNode = p1;
    p1.nextNode = p2;
    p2.nextNode = p3;
    p3.nextNode = p4;
    
    WLPoly *polyB = [[WLPoly alloc]init];
    WLPoly *q1 = [self nextNodeWithCoef:5 exp:13];
    WLPoly *q2 = [self nextNodeWithCoef:3 exp:11];
    WLPoly *q3 = [self nextNodeWithCoef:8 exp:6];
    WLPoly *q4 = [self nextNodeWithCoef:5 exp:4];
    polyB.nextNode = q1;
    q1.nextNode = q2;
    q2.nextNode = q3;
    q3.nextNode = q4;
    
    [WLPoly printPoly:polyA];
    [WLPoly printPoly:polyB];
    //[WLPoly addPoly:polyA poly2:polyB];
}

2.雙向鏈表
在單鏈表中皱埠,可以找到任意一個節(jié)點(diǎn)的后繼節(jié)點(diǎn)肮帐,但是無法找出他的前趨節(jié)點(diǎn),這是單鏈表的缺點(diǎn)边器。
在雙向鏈表中泪姨,每個節(jié)點(diǎn)中,除了數(shù)據(jù)字段外饰抒,還包含了兩個指針肮砾,一個直線該節(jié)點(diǎn)的前趨節(jié)點(diǎn),一個指向該節(jié)點(diǎn)的后繼節(jié)點(diǎn)袋坑。有兩個好處:一個是可以從頭和尾搜索某個節(jié)點(diǎn)仗处。二是如果某一條鏈?zhǔn)Я耍€可以利用另一條鏈修復(fù)整個鏈表枣宫。
(1)插入一個新的節(jié)點(diǎn)
設(shè)p是雙向鏈表中的一個節(jié)點(diǎn)婆誓,pre代表前趨節(jié)點(diǎn),next代表后繼節(jié)點(diǎn)也颤,s指向值為x的新的節(jié)點(diǎn)洋幻。操作步驟如下:

s->pre = p->next;//1
p->pre ->next = s;//2
s->next = p;//3
p->pre = s;//4

示意圖如下


插入新節(jié)點(diǎn).png

(2)刪除一個節(jié)點(diǎn)
設(shè)p是雙向鏈表中的一個節(jié)點(diǎn),pre代表前趨節(jié)點(diǎn)翅娶,刪除p節(jié)點(diǎn)文留,操作步驟如下:

p->pre->next = p->next;//1
p->next->pre = p->pre;//2
free(p);

示意圖如下


刪除節(jié)點(diǎn).png

3.循環(huán)鏈表
單循環(huán)鏈表是在單鏈表基礎(chǔ)上好唯,將表頭指針放入鏈表尾部節(jié)點(diǎn)的指針域中,這就構(gòu)成了單循環(huán)鏈表了燥翅。單循環(huán)鏈表可以從表中任意一個節(jié)點(diǎn)開始遍歷整個鏈表骑篙。
在實(shí)際應(yīng)用中,一般使用尾指針代替頭指針來進(jìn)行某種操作森书,比如靶端,將兩個單循環(huán)鏈表首尾相連。

設(shè)定單循環(huán)鏈表hr1凛膏、hr2杨名,它們的尾節(jié)點(diǎn)分別為r1、r2猖毫,操作語句如下
r2->next = r1->next;//將第一個鏈表的尾節(jié)點(diǎn)的下一個節(jié)點(diǎn)指針接入到第二個鏈表的尾節(jié)點(diǎn)中
r1->next = hr2->next;//將第二個鏈表的頭節(jié)點(diǎn)的下一個節(jié)點(diǎn)指針接入到第一個鏈表的尾節(jié)點(diǎn)中
free(hr2);//釋放第二個鏈表
r1 = r2;//合并成一個單循環(huán)鏈表镣煮,只有一個尾節(jié)點(diǎn)

操作示意圖如下


操作示意圖.png

三、如何選擇存儲結(jié)構(gòu)

通潮陕螅基于以下三點(diǎn)考慮

1.存儲空間

順序表的存儲空間,在運(yùn)行前必須聲明大小镊折,也就是說胯府,必須設(shè)定一個最大值,如果最大值設(shè)置過大恨胚,則會造成資源浪費(fèi)骂因;如果設(shè)置過小,則容易溢出(這里不考慮擴(kuò)容)赃泡。如果需要實(shí)現(xiàn)擴(kuò)容寒波,則需要重新創(chuàng)建一個更大的數(shù)組,然后將原數(shù)組中的元素拷貝到新的數(shù)組中升熊,開銷比較大俄烁。
鏈表不需要考慮存儲空間大小,但鏈表的存儲密度較低(存儲密度是指一個節(jié)點(diǎn)中數(shù)據(jù)元素所占的存儲單元和整個節(jié)點(diǎn)所占存儲單元之比)级野。顯然鏈表的存儲密度小于1页屠。

2.運(yùn)算

順序表中,訪問第i個元素的時間復(fù)雜度為O(1)蓖柔,而鏈表中訪問第i個元素的時間復(fù)雜度為O((n+1)/2)辰企。所以,頻繁訪問元素况鸣,則順序表優(yōu)于鏈表牢贸。從插入刪除角度考慮,則鏈表優(yōu)于順序表镐捧。

3.運(yùn)行環(huán)境

順序表比較容易實(shí)現(xiàn)潜索,任何高級語言中都有數(shù)組類型臭增,鏈表的操作是基于指針,相對來說帮辟,前者更簡單一點(diǎn)速址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市由驹,隨后出現(xiàn)的幾起案子芍锚,更是在濱河造成了極大的恐慌,老刑警劉巖蔓榄,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并炮,死亡現(xiàn)場離奇詭異,居然都是意外死亡甥郑,警方通過查閱死者的電腦和手機(jī)逃魄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澜搅,“玉大人伍俘,你說我怎么就攤上這事∶闾桑” “怎么了癌瘾?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長饵溅。 經(jīng)常有香客問我妨退,道長,這世上最難降的妖魔是什么蜕企? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任咬荷,我火速辦了婚禮,結(jié)果婚禮上轻掩,老公的妹妹穿的比我還像新娘幸乒。我一直安慰自己,他們只是感情好唇牧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布逝变。 她就那樣靜靜地躺著,像睡著了一般奋构。 火紅的嫁衣襯著肌膚如雪壳影。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天弥臼,我揣著相機(jī)與錄音宴咧,去河邊找鬼。 笑死径缅,一個胖子當(dāng)著我的面吹牛掺栅,可吹牛的內(nèi)容都是我干的烙肺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼氧卧,長吁一口氣:“原來是場噩夢啊……” “哼桃笙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沙绝,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搏明,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闪檬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體星著,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年粗悯,在試婚紗的時候發(fā)現(xiàn)自己被綠了虚循。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡样傍,死狀恐怖横缔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衫哥,我是刑警寧澤茎刚,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站炕檩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捌斧。R本人自食惡果不足惜笛质,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捞蚂。 院中可真熱鬧妇押,春花似錦、人聲如沸姓迅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丁存。三九已至肩杈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間解寝,已是汗流浹背扩然。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聋伦,地道東北人夫偶。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓界睁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兵拢。 傳聞我的和親對象是個殘疾皇子翻斟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容