数据结构之链表精讲,数据结构之链表golang

  数据结构之链表精讲,数据结构之链表golang

  博客明星评选

  写完前面的序列表,这就是链表。这里不得不提一下链表的优点。没办法,优秀的东西总是要比较的。今天的任务并不简单。我们有很多东西要学。而本,这也是我们找工作时必考的知识点。

  在链表《漫画算法:小灰的算法之旅》这本书里,举了一个很有意思的例子。它把有序列表比作正规军,链表就是地下党,非常形象。有序列表的物理结构是数组,我们按顺序存储,而链表是随机存储的。有时候我们只能找到它的上级和下级,不能为它而跳,就像地下工作者一样。

  由于数据结构的模块大部分是用C语言编写的,学习这个需要对结构、指针和内存管理有充分的了解。所以我们的C语言知识一定要扎实。

  什么是链表?链表是物理存储结构中不连续、无序的存储结构。数据元素的逻辑顺序是通过链表中指针链接的顺序实现的。

  下面是一个结构。

  typedef int SLDataType//数据类型typedef structs listnode { sldatatypeval;struct SListNode * next//记录下一个节点的地址} SListNode

  我们来看一个完整的单向链表。下面是我们要学习的知识点。

  我们想知道为什么有一个链表,因为有一个顺序列表。为什么我们需要一个链表?这是因为序列表存在一些缺陷。

  中间/头的插入和删除,时间复杂度为O(N)。容量扩展需要申请新空间、复制数据和释放旧空间。会有很多消耗。一般容量提升2倍,必然会造成一定的空间浪费。比如现在的容量是100,满了就增加到200。我们

  又插入了5个数据,后面没有插入数据,浪费了95个数据空间。再来看扩容的缺陷。当我们使用realloc进行扩容时,如果后面增加的空间过大,就会出现==异地扩容==,导致不必要的消耗。

  void test(){ int * P1=(int *)malloc(40);printf(%p\n ,P1);p1=(int*)realloc(p1,100000);printf(%p\n ,P1);免费(P1);} int main(){ test();返回0;}

  分类链表也有一定的分类,但本质是一样的,可以分为八种。

  是带头单向还是双向循环单链表。先说单向链表不占主导。大家先学基础会比较容易。

  在C语言中,我们需要定义一个结构,其成员具有有效数据和下一个节点的地址。如上所述,这里我们主要使用链表方法。

  为了通过打印来测试代码的正确性,我们先来看看如何打印单链表,然后帮我们验证代码的正确性。

  我们定义一个cur来指向链表的头节点。如果我们一次都没有打印出来,就让cur=cur- next,这样我们就可以打印出整个链表了。

  void slist print(slist node * pHead){ slist node * cur=pHead;而(cur!=NULL) { printf(%d -,cur-val);cur=cur-next;} printf( NULL \ n );}插入数据一般来说,单链表数据的插入分为头插入和尾插入。一般我们很少用其他的插入方法,不是说这些方法不能实现,而是没有太大的价值。

  插件就是在链表前面插入另一个节点。有两种情况。

  链表是空链表,插入的节点是头节点。链表是正常的,插入一个节点。

  我们需要解释代码,并有以下问题。

  为什么要传入二级指针?如果我们第一次插入头,需要改变头节点的地址,引入一级指针(头节点的地址)。改变的是参数为什么定义buys list node(x);我们发现后面的尾部插入也需要创建一个节点,这样可以避免代码重复做一个轮子。我们单独拿出了slistnode * BuysListNode (sl数据类型x){ slist node * node=(slist node *)malloc(sizeof(slist node));if(node==NULL){ printf( malloc fail!\ n’);退出(-1);} node-next=NULL;node-val=x;返回节点;} void SListPushFront(slist node * * pplist,sl datatype x){ assert(pplist);slist node * node=buys list node(x);//判断是否第一次插入if(* pplist==NULL){ * pplist=node;返回;} node-next=* pplist;* pplist=node}测试一下吧?稍后我们将讨论打印功能。

  int main(){ slist node * pHead=NULL;slist print(pHead);SListPushFront( pHead,3);SListPushFront( pHead,2);SListPushFront( pHead,1);slist print(pHead);返回0;}

  尾塞说完了,再来说说尾塞。一般来说,尾塞比头塞多一点。我们一般在尾部插入数据,这样更符合我们的想法。

  尾插入是将一个节点插入链表的尾节点。在完成尾部插入之前,我们需要首先找到尾部节点,以便插入它。

  还使用了二级指针,因为当我们第一次插入数据时,我们需要改变头节点的位置。

  void slist push back(slist node * * PP head,sl datatype x){ assert(PP head);slist node * node=buys list node(x);//第一次插入if(* PP head==null){ * PP head=node;返回;} SListNode * cur=* ppHeadwhile (cur- next!=NULL){ cur=cur-next;} cur-next=node;}删除数据插入数据已经完成,现在我们来看看如何删除数据。

  头删除意味着删除第一个节点。在序列表中,我们可能要移动很多数据,但单链表是不必要的。你会发现它的优点。

  void SListPopFront(slist node * * PP head){ assert(PP head);if(* PP head==NULL){ printf( no node );返回;} slist node * cur=(* PP head)-next;免费(* PP head);* ppHead=cur}测试

  int main(){ slist node * pHead=NULL;SListPushBack( pHead,1);SListPushBack( pHead,2);SListPushBack( pHead,3);slist print(pHead);SListPopFront(pHead);slist print(pHead);slist destory(pHead);返回0;}

  尾删除列表的尾删除和尾插入一样,需要找到最后一个节点,但是尾删除需要将倒数第二个节点的next设置为NULL,这是必须要做的。

  void SListPopBack(slist node * * PP head){ assert(PP head);if(* PP head==NULL){ printf( no node );返回;} SListNode * cur=* ppHead//只有一个节点if(cur-next==NULL){ free(cur);* ppHead=NULL返回;} slist node * cur next=cur-next;while (curNext- next!=NULL){ cur=cur next;cur next=cur next-next;}免费(cur next);cur-next=NULL;}测试

  int main(){ slist node * pHead=NULL;SListPushBack( pHead,1);SListPushBack( pHead,2);SListPushBack( pHead,3);slist print(pHead);SListPopBack(pHead);slist print(pHead);slist destory(pHead);返回0;}

  寻找数据,我们可以检查val的值在单链表中是否相同。如果相同,我们将返回该节点的地址。如果不是,我们将返回NULL。数据搜索在现实生活中起着重要的作用。

  slist node * slist find(slist node * pHead,sl datatype key){ assert(pHead);SListNode * cur=pHead而(cur!=NULL){ if(cur-val==key){ return cur;} cur=cur-next;}返回NULL}测试代码

  int main(){ slist node * pHead=NULL;SListPushBack( pHead,1);SListPushBack( pHead,2);SListPushBack( pHead,3);SListNode* ret=SListFind(pHead,2);如果(ret!=NULL) { printf(%d ,ret-val);} slist destory(pHead);返回0;}

  在pos位置之后的X前面插入单个链表。我们谈到了在查找数据时返回节点的地址,这是这样做的基础。但是我们要想一想,为什么不在pos前面插入数据呢?很简单。我们使用单一的链表。后面节点的地址很容易得到,但是如果要得到前面的地址,请慢慢玩。

  void SListInsertAfter(slist node * pos,sl datatype x){ assert(pos);slist node * cur=pos-next;slist node * node=buys list node(x);pos- next=节点;node-next=cur;}测试

  int main(){ slist node * pHead=NULL;SListPushBack( pHead,1);SListPushBack( pHead,2);SListPushBack( pHead,3);slist print(pHead);SListNode* ret=SListFind(pHead,2);SListInsertAfter(ret,0);slist print(pHead);slist destory(pHead);返回0;}

  单链表中删除pos位置后的值我们刚才说了pos后插入,现在要说删除了。这些都很简单。

  void slisterasafter(slist node * pos){ assert(pos);slist node * cur=pos-next;//判断pos是否为尾节点if(cur==NULL){ return;} pos-next=cur-next;免费(cur);}测试

  int main(){ slist node * pHead=NULL;SListPushBack( pHead,1);SListPushBack( pHead,2);SListPushBack( pHead,3);slist print(pHead);SListNode* ret=SListFind(pHead,1);slisterasafter(ret);slist print(pHead);slist destory(pHead);返回0;}

  有些破坏链表的人可能会疑惑,为什么我们最后还要破坏链表?我们节点的空间都是来自malloc,用完后要释放,否则会出现内存泄漏。也许你觉得这个内存不算什么,但是如果我们的链表太长,次数太多,服务器的内存就会一点一点变小。最后会被领导骂。

  void slist destory(slist node * * PP head){ assert(PP head);SListNode * cur=* ppHead而(cur!=NULL){ slist node * cur next=cur-next;免费(cur);cur=curNext} * ppHead=NULL}双头循环链表,单链表。我们已经详细说过了,但是单链表还是有一定的缺陷。

  当我们无法得到前一个节点的地址后缀时,需要遍历整个链表,时间复杂度为O(N)。这些都是问题。我,大家想想有没有链表来解决这些问题。我很高兴双向循环链表可以解决这些问题。我们这里的方法和上面的差不多,这里就不做动画了。不过,我会用文字详细描述。

  带前沿的双向循环链表:最复杂的结构一般用来单独存储数据。实际上,中使用的链表数据结构既是前导的又是双向的。

  循环链表。另外,虽然这个结构很复杂,但是会发现用代码实现的时候结构会带来很多好处,反而是实现

  很简单。当我们的代码实现后,我们就知道了。

  Typedef int LTDataType节点;typedef结构ListNode { LTDataType datastruct ListNode * nextstruct ListNode * prev} ListNode

  在头节点上创建一个单链表。我们没有增加一个标兵。这里补充一下。

  既然是双头循环链表,那么头节点也一定是循环的。

  ListNode * list create(){ ListNode * cur=(ListNode *)malloc(sizeof(ListNode));assert(cur);cur-data=-1;cur-next=cur;cur-prev=cur;返回cur} int main(){ ListNode * pace setter=list create();//标兵返回0;}

  和以前一样,我们需要测试代码的正确性。先来说说打印的功能。

  创建一个节点,指向下一个标兵并打印这个值。这个节点向后移动,直到节点的地址等于pace setter void list print(list node * plist){ assert(plist);ListNode * cur=plist-next;而(cur!=plist) { printf(%d-,cur-data);cur=cur-next;} printf( NULL \ n );}插入元素头需要完成以下步骤,

  创建一个新节点。新节点的下一个保存原始的头节点。原始头节点的prev保存新节点的标兵。下一步保存新节点的位置。新节点的prev保存标兵的void listpushfront(listnode * plist,lt datatype x){ listnode * node=(listnode *)malloc(sizeof(listnode));断言(节点);node-data=x;node-next=plist-next;node-next-prev=node;plist-next=node;node-prev=plist;}

  尾部插入更简单。使用单链表时,需要一步一步寻找尾节点。这里不需要O(N)的时间复杂度。我们可以观察到,定标器位的prev是尾节点的地址。

  创建一个节点,将一个节点指向尾节点,依次修改尾节点和标兵的对应值:void list Push Back(listnode * plist,lt datatype x){ listnode * node=(listnode *)malloc(sizeof(listnode));断言(节点);node-data=x;ListNode * tail=plist-prev;//尾节点的next指向节点tail-next=node;//节点的prev指向尾节点-prev=tail;//next of node指向标兵node-next=plist;//标兵的prev指向节点plist-prev=node;}

  删除元素标题。记住头删除不是模型删除,而是有效节点删除。

  取一个节点记录头节点,判断该头节点是否是与标兵位置相同的记录头节点的下一个节点,依次修改相应的值。void ListPopFront(list node * plist){ assert(plist);ListNode * head=plist-next;if(head==plist){ return;} ListNode * head next=head-next;head-next=NULL;head-prev=NULL;免费(头);plist-next=head next;head next-prev=plist;}删尾删尾很简单。我们可以一步找到最后一个尾节点,修改对应的next和prev,这里就不跟大家分享了。

  void ListPopBack(list node * plist){ assert(plist);ListNode * tail=plist-prev;if(tail==plist){ return;} tail-prev-next=plist;plist-prev=tail-prev;tail-next=NULL;tail-prev=NULL;自由(尾巴);}

  这里总结一下,应该对链表的理论有一定的了解,但这远远不够。我们需要一些练习来练习和锻炼我们的思维。在这里,我给大家列举几个简单的OJ问题。只有答案,没有解释。但是,这些都比较简单。让我们坐下来提高我们的能力。

  简单链表练习集

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • mysql复合索引和组合索引,mysql组合索引数据结构
  • mysql复合索引和组合索引,mysql组合索引数据结构,Mysql之组合索引方法详解
  • mysql复合索引和组合索引,mysql复合索引数据结构
  • mysql复合索引和组合索引,mysql复合索引数据结构,MySQL的复合索引总结
  • b+树 多路搜索树,数据结构中树的分类
  • b+树 多路搜索树,数据结构中树的分类,数据结构-树(三):多路搜索树B树、B+树
  • avl树的构造,avl树特性,数据结构之AVL树详解
  • 数据结构c语言哈夫曼树,c语言哈夫曼树的构造,使用C语言详解霍夫曼树数据结构
  • c语言数据结构算法编程库,数据结构 c语言中文网
  • c语言数据结构算法编程库,数据结构 c语言中文网,C语言编程数据结构基础详解小白篇
  • c++纸牌游戏,数据结构纸牌游戏c语言
  • c++纸牌游戏,数据结构纸牌游戏c语言,C语言实战之纸牌游戏
  • ,,c#解析jobject的数据结构
  • ,,javascript数据结构之多叉树经典操作示例【创建、添加、遍历、移除等】
  • ,,Java 数据结构与算法系列精讲之背包问题
  • 留言与评论(共有 条评论)
       
    验证码: