大话西游之王道考研数据结构第一讲---线性表的顺序表示

大话西游之王道考研数据结构第一讲---线性表的顺序表示

写在前面的话

王道考研数据结构是一本非常好的书,本系列所有的内容是按照其书进行讲述的,所以您可以以那本书作为主要内容,这个做参考。

大学时候,在网上看到了一个人通过讲三国的形式把并查集(union find)讲的非常清楚且很有意思。我很喜欢这样的方式,所以一直想着能够利用相似的方式把我对数据结构的理解表达出来。

这样的方式可能并不能够被所有人接受,内容也会很不严谨,如果有错误的地方请多多包涵。我希望能够帮到一些很讨厌数据结构的人,至少能够让数据结构看起来不是很枯燥。

恰好最近在给一个同学补习考研数据结构,所以我想一直更新下去,感谢您的支持。

                                        第二章 线性表

2.1 线性表的定义

      线性表就是一堆类型的n(n>=0)个数据元素的有限序列。

                                                        (注意下标)

举个栗子:

         把唐僧师徒5人看成数据元素,他们准备成立一家公司。

        首先,公司得去工商局注册,我们得告诉工商局公司最大打算招多少员工(500人)。

     

      公司当然得分老大老二了。唐僧肯定是老大了。

 

老大

老二

老三

老四

老五

唐僧 猴哥 八戒 沙僧 马马

                  

                

         接下来就是猴哥、八戒、沙僧、马马。

 特点:

1.公司最大人数有限(最多500个人,再多可能以为是P2P)

(表中元素个数有限)

2.团队内部等级分明,是有次序的

(表中元素具有逻辑上的顺序性,在数列中各元素排序已有其先后次序)

3.人总得有个住处,公司得登记大家住处,所以每一个人一个房子(再牛的老大也就一个房子。。。)

(每个元素相同大小的存储空间)

4.无论高低贵贱,在一起就是兄弟,就能排上号,并不是因为年龄大就辈分大。

(元素具有抽象性,仅讨论元素间的逻辑关系,不考虑元素究竟表示什么内容,也就是如果存储内容是年龄的话,并不是因为年龄大就在前面)

线性表有两种表示方法,一种是顺序表示,一种是链式表示。

2.2 线性表的顺序表示

2.2.1 顺序表的定义

线性表的顺序存储又称为顺序表,表中元素的逻辑顺序与其物理顺序相同

举个栗子:

这个公司比较有钱,他们属于单位分配房子,所以大家都在一个小区,唐僧的门牌号是(62000,62可以是小区的编号),并且从这个编号往后数500位都是你们的编号,自行分配。但是哪个房子住谁都得给工商局备案,并且为了公平,大家按照辈分依次分房。

唐僧是老大,所住房子是62000。公司内部为了简单起见,把前面的编号省去,所以他是0。

0

1

2

3

4

老大

老二

老三

老四

老五

唐僧 猴哥 八戒 沙僧 马马

 

 

 

 

2.1.2 顺序表上的基本操作与实现

首先,我们定义一下公司的内容。

#define MAXSIZE 500    //公司最大规模
typedef struct
{
    int *seat;  //交椅(编号)-人
    int length; //当前公司有多少个人

}SqList;

         1.申请公司~,工商局得有个准备工作,看看给你的编号段之前有没有信息,(比如有个公司原来是这个编号段,但是倒闭了,信息还在,但是可以分配给新的公司),有的话清除掉(把倒闭公司的信息抹掉)。

                   (InitList(&L):初始化表,构造一个空的线性表)

void InitList(SqList &sqList){

   sqList.seat = (int *)malloc(sizeof(int)*MAXSIZE); //咱直接申请最大规模的公司

   sqList.length = 0;    //当前公司人数是0个,因为还没分老大老二呢
}

         2.看下这个公司当前有多少人。

                    (Length(L):求表长,返回线性表L的长度,即L中数据元素的个数)

int Length(SqList sqList){

    return sqList.length;
}

         3. 看下马马的房子编号是多少

                   (LocateElem(L,e):按值查找操作,e代表人)

int LocateElem(SqList sqList,int e){
    int locate = -1;

    for(int i =0;i<sqList.length;i++){ //我们在找马马
        if(sqList.seat[i] == e){ //找到了
            locate = i; 
            return locate; //返回马马的编号
        }
    }
    return locate; //如果找不到的话,就返回-1
}

         4.看下编号是4的人叫啥

                   (GetElem(L,i):按位查找操作,i代表编号)

int GetElem(SqList sqList,int i){

    return sqList.seat[i];

}

随机访问,可以通过首地址和元素符号在O(1)复杂度下找到那个人

         5.来了一位能力超群大佬想加入公司,并且要求你必须给我老三的位置

                   (ListInsert(&L,i,e):把e插入到第i个位置)

1.插入的位置对不对

2.够不够插

3.往后挪

4.插!

5.修改总人数length

6.返回true

                   (&表示人员变动得上报工商局,不然罚款)

bool ListInsert(SqList &sqList,int i,int e){
    /*加入一个人当老i*/
    bool isSucceed = false; //先让它置为false,基本操作

    if(i<1 || i>sqList.length+1){  //首先,我们检查第i个位置是否在这个团队中,比如唐僧是老大(位置是0),马马是老五个人(位置是4)
    //如果我们输入的i<1 是不存在老0 老-1 之类的 所以返回flase
    //如果我们输入的i>L.length+1 比如 i = 7 现在最小老五, 来个人想当老7 也是不合理的, 返回false
        return isSucceed;
    }

    if(sqList.length + 1 > MAXSIZE){ //如果队伍人数超过之前申请的最大人数 队伍满啦 再来小弟大家就没饭吃了 所以返回false
        return isSucceed;
    }

    for(int j = sqList.length;j>=i;j--){ //比如来个人当老二,那么马马 沙僧 八戒都得往后排
        //想想为什么是倒序
        sqList.seat[j+1] = sqList.seat[j];
    }

    sqList.seat[i-1] = e;  //这把交椅就是兄弟你的了

    sqList.length++;  //总人数加1

    isSucceed = true; //插入成功~

    return isSucceed;

}

         6.      老二猴哥表示这破公司,我TM(提莫~)待不下去了,俺老孙去也~

1.位置对不对

2.挪!  为什么不执行删的步骤

3.修改人数

4.返回true

                   (ListDelete(&L,i,&e):删除第i个位置的人,并用e把这个人返回,”&”---还得上报工商局)

bool ListDelete(SqList &sqList, int i,int &e){
    /*把老i踢出公司*/
    bool isSucceed = false; //先让它置为false,基本操作

    if(i<1 || i>sqList.length+1){  //首先,我们检查第i个位置是否在这个团队中,比如唐僧是老大(位置是0),马马是老五个人(位置是4)
    //如果我们输入的i<1 是不存在老0 老-1 之类的 所以返回flase
    //如果我们输入的i>L.length+1 比如 i = 7 现在最小老五, 想踢老7 也是不合理的, 返回false
        return isSucceed;
    }

    //想想这里为什么不检查是否超出公司最大人数,但在ListInsert里面检查了

    e = sqList.seat[i-1]; //用e返回这个人,想想e传参时候为什么加地址

    for(int j = i;j<sqList.length;j++) //为什么是正序
    {
        sqList.seat[j-1] = sqList.seat[j]; //为什么是j-1,想想老大和老大的位置之间的关系
    }
    sqList.length--; //走了一个,总人数减1

    isSucceed = true;
    return isSucceed;
}

         7.      公司开会,我来点个名

                   (PrintList(L):输出操作)

void PrintList(SqList sqList){
    for(int i =0;i<sqList.length;i++){
        printf("%d ",sqList.seat[i]); //加个空格优雅美观大方楚楚动人
    }
    printf("\n"); 
}

         8.      是不是空壳公司哇~

                   (Empty(L):判空操作)

bool Empty(SqList sqList){
    if(sqList.length == 0)
        return true;
    else
        return false;
}

         9.  浙江皮革厂倒闭啦,WBDHHLBDZXYZPL

                   (DestroyList(&L):销毁操作,”&”--这必须上报工商局)                  

void DestroyList(SqList &sqList){
    sqList.length = 0;
    free(sqList.seat); //这里不能直接释放sqList,至于原因emmmmm我也不晓得哎~
}

课后习题附加

1.理论部分

1.1逻辑结构和存储结构的区别

选择题第2题有涉及到这个内容,这里说明一下:

  • 什么是逻辑结构

逻辑结构就是数据之间的关系。而按数据之间的关系来说,逻辑结构大概可以分为两种:线性结构非线性结构(集合、树、网)

  • 线性结构:有且只有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前驱和一个直接后继。例如:线性表,典型的线性表有:顺序表、链表、栈(顺序栈、链栈)队列(顺序队列、链队列)。它们共同的特点就是数据之间的线性关系,除了头结点和尾结点之外,每个结点都有唯一的前驱和唯一的后继,也就是所谓的一对一的关系。

举个栗子

唐僧是老大,猴哥是老二,他俩就是前驱和后继的关系,而且老K的前驱是老K-1,后继是老K+1,是唯一的。

老大

老二

老三

老四

老五

唐僧 猴哥 八戒 沙僧 马马
  • 非线性结构:对应于线性结构,非线性结构也就是每个结点可以有不止一个直接前驱直接后继。常见的非线性结构包括:树(二叉树)、图(网)等。

举个栗子

猴哥下面有很多猴子猴孙,比如猴哥生了2个猴(A,B),每一个猴又生了2个猴(A生(a1,a2),B生(b1,b2)),那么A的后继是a1,a2,这就不是线性结构了,这是树形结构:

 

  • 什么是存储结构

逻辑结构指的是数据间的关系,而存储结构是逻辑结构的存储映像。通俗的讲,可以将存储结构理解为逻辑结构用计算机语言的实现。常见的存储结构有顺序存储、链式存储、索引存储以及散列存储(哈希表)

  • 顺序存储:把逻辑上相邻的节点存储在物理位置上相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储结构为顺序存储结构,通常顺序存储结构是借助于数组来描述的。优点:节省空间,可以实现随机存取;缺点:插入、删除时需要移动元素,效率低。

举个栗子

这里说的是我们之前提到的编号问题,比如唐僧编号是0,猴哥编号是1。他俩的地址是相邻的。也可以理解为他们都住在计算机的内存里,他俩是邻居。

  • 链式存储:在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。特点是元素在物理上可以不相邻,所以每个数据元素包括了一个数据域和一个指针域,数据域用来存放数据,而指针域用来指向其后继结点的位置。优点:插入、删除灵活;缺点:不能随机存取,查找速度慢。

举个栗子

这个是即将提到的链式存储,可以理解为唐僧在一个地方,猴哥在另一个地方。他俩不一定是邻居。

1.2 随机存取和顺序存取的区别

  • 顺序存取:就是存取第N个数据时,必须先访问前(N-1)个数据 (链表)

举个栗子 

原来打电话时候,我们不能直接获取对方的电话号码,需要先拨对方公司电话转接处,转接处知道对方的电话,所以帮你转接到对方座机。得有个顺序~

  • 随机存取:就是存取第N个数据时,不需要访问前(N-1)个数据,直接就可以对第N个数据操作 (线性表)

举个栗子

自从有了小天才电话手表,打电话再也不需要转接了,直接拨打对方号码,因为我们知道对方号码是多少,至于为什么知道——因为是小天才电话手表哇~ 随机这个词起的不是很好理解,姑且理解为你随便打个电话,就能打到当事人把~原来是只能打到转接处。

2.代码题

2.1 设计一个高效的算法,讲顺序表的所有元素逆置,要求算法空间复杂度为O(1)

注意,本题提到的是算法空间复杂度,也就是我们需要额外开辟的空间只能是一个元素的空间。置于时间上面没有要求。而逆置则是将原来的顺序颠倒,比如:

老大

老二

老三

老四

老五

唐僧 猴哥 八戒 沙僧 马马

 

 

最后的输出结果为:

老大

老二

老三

老四

老五

马马 沙僧 八戒 猴哥 唐僧

 

 

 

思路很关键,我们可以完全新开辟一个线性表,然后原来这个表按照从后向前,依次复制给新的线性表。但是新的线性表的空间为O(n),不符合要求。

也可以这么做,老大和老五交换,老二和老四交换,老三和老三交换。这样是不是就好了,怎么交换呢,之前在冒泡排序时候,相信大家都有这么一个操作。

void InversionList(SqList &sqList){
    int s = 0;
    int e = sqList.length-1;
    while(e>s){
        int temp = sqList.seat[s];
        sqList.seat[s] = sqList.seat[e];
        sqList.seat[e] = temp;
        s++;
        e--;
    }
}

3.长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,把其中所有值为x的元素删掉。

时间复杂度为O(n),一看到这个,我们因该想到遍历一遍顺序表的复杂度是O(n),所以我们只能遍历一遍,空间复杂度是O(1),说明我们只能额外开辟一个、两个空间(注意O(1)指的是开辟的空间个数是不变的,一个两个都算O(1)),就像O(2) = O(1) = O(100) ),和上题类似。

很容易想到的是,遍历一遍,遇到x的话,把后面的往前移,然后删掉x。然后我们再遍历一遍,遇到x的话,把后面的往前移,然后删掉x。然后......直到有一遍中没有x。

这是完全不符合要求的,但是只要你能把这个写出来,10分可以拿8分(改卷人就是这么不尊重算法的效率)。我们可以再想一个。比如x是坏果子,其他的是好果子,现在果子排成一排。我们要是能把好果子都放在最前面,坏果子都放在最后面。那么这个问题就解决了。

我们可以不考虑坏果子,从头到尾开始检查,遇到一个好果子就放在第一个位置,遇到一个就放在前面那堆好果子的后面。并且记录下一共有多少个好果子。这样好果子最后都在最前面了。自然坏果子都在后面了。

整体上空间复杂度是O(1),时间复杂度O(n)

void DeleteX(SqList &sqList, int x){
    int j = 0; //记录最前面好果子的最后一个位置
    for(int i = 0;i<sqList.length;i++){
        if(sqList.seat[i] != x){

            sqList.seat[j] = sqList.seat[i];
            j++;
        }
    }
    sqList.length = j;

}

 

 

总结

顺序表的特点:

1.随机访问

因为唐僧的编号是确定的,他是老大,如果我们想知道老三是谁,我们就可以去工商局查,查的时候得需要老三的编号。因为唐僧老大,编号62000,老三编号肯定是62002,因为他们之间不容许有空号,这就是顺序表的特定。所以一查就是沙僧。

2.存储密度高

这个得和线性表的链式表示做一个对比了,等我们讲完链式表示在说说这个问题。目前我们知道,顺序表只需要一个存内容的数组的指针,一个内容长度。

3.插入、删除需要移动大量元素

因为我们不容许编号之间有空缺,比如有一个老大,没有老二,有个老三。这是不可以哒~,所以插入一个老二时候,原来的老二以及以后的人都得往后挪一挪。删除也是一样。

线性表的顺序表示比较简单,应该要明白线性表的每个操作的含义是什么,比如插入元素,我们因该先检查能不能插入,然后再插入,最后在返回结果。每个人写的代码都不一样,而且我们也背不会代码,所以思路是关键!。我上传了上述的所有代码,并且有一些调试的内容在里面。

 

https://download.csdn.net/download/zhangbaodan1/10555510

也可以在gitHub上面下载,这里是免费的

https://github.com/zhangbaodan/DataStructure

猜你喜欢

转载自blog.csdn.net/zhangbaodan1/article/details/81142428
今日推荐