《数据结构》第八篇、线性表中的链式存储(链表)

非常人贩3.jpg

顺序表必须占用一块事先分配好的、大小固定的存储空间,不便于存储空间的管理,为此有人提出可以实现存储空间的动态管理,即链式存储方式——链表。
本篇文章将学习下什么是链表,以及链表的实现。

链表存储的原理

和顺序存储不同,在链式存储中,结点之间的存储单元地址可能是不连续的。链式存储中每个结点都包含了两个部分:

存储元素本身的数据域

存储结点地址的指针域

我们在前边讲解连式存储时,提到了一些链式存储的原理,结点中的指针指向的是下一个结点,如果结点中只有指向后继结点的指针,那么这些结点组成的链表成为单向链表。
还是来张图复习一下撒

单向链表.png

一般在链表中也会有一个头结点来保存链表信息,然后有一个指针指向下一个结点,下一个结点又指向他后边的一个结点,如果这个指针没有后继结点,那么他就指向NULL。
在链表中,这些存储单元可以是不连续的,因此他可以提高空间利用率。当需要存储元素时,哪里有空闲的空间就在哪里分配,只要将分配的空间地址保存到上一个结点就可以。这样通过访问上一个元素就能找到后一个元素。

挡在链表中某一个位置插入元素时,从空闲空间中为该元素分配一个存储单元,然后将两个结点之间的指针断开,上一个结点的指针指向新分配的存储单元,新分配的结点中指针指向下一个结点。这样不需要移动原来元素的位置,效率比较高。同样,当删除链表中的某个元素时,就断开它与前后两个结点的指针,然后他的前后两个结点连接起来,同样也不需要移动原来元素的位置。与顺序表相比,在插入、删除元素方面,链表的效率要比顺序表高许多。
但是,随机查找元素时,由于链表没有像顺序表的索引标注,存储单元的空间并不连续,如果要查找某一个元素,必须先得经过他的上一个结点中的地址才能找到他,因此不管遍历哪一个元素,都必须要把他前面的那个元素都遍历后才能找到他,效率就不如顺序表的高了。


总结一句话,链表增删元素的效率比较高,但是查找元素的效率就比顺序表低了。

链式存储的实现

链表的几种操作与顺序表差不多,也是增删改查等操作,接下来我们实现一个链表。

1、创建链表(根据上图的单向链表创建)

在创建链表时,头结点中保存链表的信息,则需要创建一个结构体struct,在其中定义链表的信息与指向下一结点的指针。代码如下:

    struct Header{ //头结点
    int length;//记录链表的长度
    struct Node * next;//指向第一个结点的指针 
    }

存储元素结点包含两部分内容:数据域和指针域,则也需要再定义一个struct,代码如下:

struct Node{//结点
int data;//数据域
struct Node * next;//指向下一个结点的指针
}

这样头结点与数据结点均已定义,为了使用方便,将两个struct用typedef重新定义新的名称,代码如下

  typedef struct Node List;//把struct Node重命名为List
  typedef struct Header pHead;//把struct Header 重命名为pHead

创建链表要比创建顺序表简单一些。
顺序表中需要先为头结点分配空间,其次为数组分配一段连续空间,将这段连续空间地址保存在头结点中,然后往其中存储数据。
而创建链表时,只需要创建一个头结点,每存储一个元素就分配一个存储单元,然后将存储单元的地址保存在上一个结点的指针中即可,不需要再创建时把所有空间都分配好。
ok,我们把两个结构体定义好之后,就可以创建链表了,创建链表的代码如下:

  pHead * createList(){//pHead 是struct Header的别名,是头结点类型
  pHead * ph=(pHead *)malloc(sizeof(pHead));//为头结点分配内存
  ph->lenght=0;//为头结点初始化
  ph->next=NULL;    
  return ph;//将头结点地址返回
  }

2、获取链表大小

链表大小等信息也保存在头结点中,因此需要从头结点中获取即可,也是很简单的:

int Size(pHead * ph){
      if(ph==NULL){
      printf(“参数传入有误”);
      return 0;
      }
      return ph->length;
}

3、插入元素

在链表中插入元素要比在顺序表中快,只需要将插入位置前后指针断开,然后让前元素指针指向新元素,新元素指针指向后元素即可。
插入元素示意图


插入元素步骤1.png
插入元素步骤2.png
插入元素步骤3.png
插入元素bingo.png

代码如下:

    int insert(pHead *ph,int pos,int val){
  if(ph==NULL||pos<0||pos>ph->length){
      printf("参数传入有误");
      return 0;
      }
  //在向链表中插入元素时,先要找到这个位置
    //先分配一块内存给要插入的数据
    List * pval=(List *)malloc(sizeof(List));
    pval->data=val;
   //当前指针指向头结点后的第一个节点
    List * pCur=ph->next;  
   //如果要插入的位置是0
    if(pos==0){
    ph->next=pval;
    pval->next=pCur;
    }
    else{
         //通过for循环找到要插入的位置
        for(int i=1;i<pos;i++){
          pCur=pCur->next;
      }
        //指针重指
        pval->next=pCur->next;
        pCur->next=pval;
    }
  //由于增加了一个元素,所以长度加一
  ph->length++;
  return 1;
  }

4、查找某个元素

查找链表中的某个元素,其效率没有顺序表高,因为不管查找的元素在哪个位置,都需要将她前边的那个元素都完全遍历才能找到。查找元素的代码如下:

  List * find(pHead * ph,int val){
        if(ph==NULL){
          printf("传入参数有误\n");
          return NULL;
        }
        //遍历链表来查找元素,从第一个元素开始遍历
        LIst * pTmp=ph->next;
        do{
                if(pTmp->data==val){
                      //一旦发现有符合条件的值,直接返回
                     return pTmp;
                 }
                //让循环动起来
                pTmp=pTmp->next;
            }
        //如果到最后都没找到符合条件的结点,说明没有
        while(pTmp->next!=NULL);
        printf("没有职位%d的元素“,val);
        return NULL;
  }

5、删除元素

再删除元素时,首相将被删除元素与上下结点之间的连接断开,然后将这两个上下结点重新连接,这样元素就从链表中成功删除了。示意图如下


删除元素步骤1.png
删除元素步骤2.png
删除元素bingo.png

我们看到,从链表中删除元素,也不需要移动其他元素,效率也比较高,我们看下代码吧

    LIst * Delete(pHead * ph,int val){
          if(ph==NULL){
              printf("链表传入错误!");
              return NULL;  
          }
          //找到val值所在的结点,这里调用了上方查找的方法
          List * pval=find(ph,val);
          if(pval==NULL){
              printf("没有值为%d的元素!",val);
              return NULL;  
          }
          //遍历链表找到要删除的结点,并找出其前驱和后继结点
          List  * pRe=ph-next;
          List  * pCur=NULL;
          //判断特殊情况:如果要删除的元素是第一个结点
          if(pRe->data==val){
              ph->next=pRe->next;
              ph->length--;
              return pRe;
          }  
          //排查特殊情况之后
          else{
                for(int i=0;i<ph->length;i++){
                    pCur=pRe->next;
                    if(pCur->data==val){
                        //执行上方图例操作
                        pRe->next=pCur->next;
                        ph->length--;
                        return pCur;
                     }  
                      //延续循环遍历
                      pRe=pRe->next;
                    }
                 }
    }

6、销毁链表

销毁链表时,将链表的每个结点元素释放。头结点可以释放,也可以保留,并将其置为初始化状态。代码如下:

     void Destory(pHead * ph){
          List * pCur=ph->next;
          List * pTmp;
          if(ph==NULL){
            printf("参数传递有误!”):
          }
          while(pCur->next!=NULL){
           pTmp=pCur->next;
          //将结点释放
           free(pCur);
           pCur=pTmp;
        }
        //将头结点置为初始化状态
        ph->length=0;
        ph->next=NULL;
     }

在本例中,没有释放头结点,只是将头结点中的信息置为初始化状态了。

7、遍历打印链表

实现出链表的遍历打印函数,代码如下:

  void print(pHead * ph){
  if(ph==NULL){
    printf(“参数传递有误!”);
  }  
  List * pTmp=ph->next;
  while(pTmp!=NULL){
     printf("%d",pTmp->data); 
     pTmp=pTmp->next;
    }
  printf("\n");
  }

至此,链表基本的操作都已实现。
现在我们对于线性表的一些相关知识:原理及实现方式,应该有了一定的认识了,其实只要了解了其中的存储原理,思路清晰,代码实现并不难。

掌握了这两个最基本的线性表,对接下来我们学习其他数据结构会有很大帮助。

谢谢关注~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容