02-链表基础

1.写在前面
众所周知,链表的最常见的数据结构之一。此外,提起链表,不得不说“数组”,这两者经常用来比较。学习链表和数组最重要
明确两者的内存布局以及在什么场合使用。下面对比下两者的区别
2.数组VS链表
2.1 数组
	内存布局:定长的连续内存空间
	优劣势:
		优势:.“下标查找”的时间复杂度为O(1)(注意是“下标查找”),即具有“随机访问”的特点
		劣势:1)增加和删除的时间复杂度为O(n),即需要移动大量的元素以腾出空间(增加)或移动大量元素以覆盖(删除)
			   2)不能动态拓展,数组空间需提前确定,需要扩容时,需重新分配
	注意:数组越界
	普及两点:
	1)数组下标为何从0开始?
	如果下标从1开始:a[k].address = base_address + (k-1)* type_size
	而下标从0开始:a[k].address = base_address + k * type_size
	相比之下,下标从0开始省去了一个减法运算,而数组效率的优化要尽可能极致,故减少一次减法操作
	
	2)有容器还需数组吗?
	如C++的vector,java的ArrayList容器类将很多操作的细节封装起来,且支持动态扩容,但最后再创建这些容器类时事先
	指定合适数据大小,因为扩容操作设计内存申请和数据搬移,是比较耗时的尽管容器类给开发带来很多方便,但数组的
	用武之地仍是有的,如如果是底层开发,性能需要优化到极致,使用数组而不是容器类。而容器类适合业务开发
	
2.2 链表
	内存布局:不需要连续,只需存储下一个节点的地址即可
	优劣势:
		优势:1)增删的时间复杂度为O(1), 速度较数组快
			  2) 扩容方便
		劣势:查找的时间复杂度为O(n),即查找代价高,不具备随机访问,需从头到尾遍历
2.3 "应用场合" 及 "空间利用率"
	应用场合:两者可以说是优势互补。在实际应用中,如果要求大量的查找,那数组更合适。反之,在大量增删操作的场合,
	链表更胜一筹。
	那如果是要求大量增删且查找呢? 可用“数组链表”,即分配的空间是连续,但以链表方式操作。(如内核的Hash链表)

	内存空间利用率:每一个数组单元是 100% 存储数据,每一个链表单元是存储数据 + 存储指针,数组对于内存的利用上大于链表
	(当然了,还需要看是否提前知道要存储数据个数)
2.4 总结一下
1.内存布局:数组分配的是连续内存,不易引起空间碎片化,但空间利用率相对链表低,且大小固定。而链表可引起空间碎片化,
			但空间使用灵活,大小可动态分配,空间利用率高,但随动态分配和释放也会降低执行效率
2.性能:数组查询快,但删除、插入慢。反之,链表插入、删除快,查询慢
		(注意:比较是相对的)
 3.复杂度:链表插入删除时间复杂度O(1),随机访问O(n),数组与之相反
3.链表的模型
链表分为 4 种情况:单链表,单循环链表,双链表,双循环链表

各自模型如下:(截图自谱哥的微信公众号“编程剑谱”,强烈推荐关注。“认真的人自带光芒”)
链表的各种模型

4. 链表的操作
说到链表的操作,CRUD(增删改查)必不可少,也是耳熟能详的话题了。
4.1 单链表
	根据有无头结点,可分为两种情况

1)带头结点(不存储真实数据)的单链表,如下图示

带头结点的单链表
2)不带头结点的单链表
不带头结点的单链表
3)下面实现一个有序单链表
头文件

#ifndef _LLIST_H_
#define  _LLIST_H_
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;
typedef struct SingleNode
{
 ElemType data;
 struct SingleNode *next;
}SingleLinkedList,*LinkList;

//单链表初始化
void ListInitialize(LinkList *head)
{
 if((*head=(SingleLinkedList*)malloc(sizeof(SingleLinkedList)))==NULL)
  exit(1);
 (*head)->next=NULL;
}
//插入
void ListInsert(LinkList head,ElemType x)
{
 LinkList curr,pre,q;
 curr=head->next;
 pre=head;
 while(curr!=NULL&&curr->data<=x)
 {
  pre=curr;
  curr=curr->next;
 }
 q=(SingleLinkedList *)malloc(sizeof(SingleLinkedList));
 q->data=x;
 q->next=pre->next;
 pre->next=q;
}
//删除数据元素
int ListDelete(LinkList head,ElemType x)
{
 LinkList p,s;
 s=head;
 p=head->next;
 while(p->next->next!=NULL&&p->next!=NULL&&x!=p->data)
 {
  s=s->next;
  p=p->next;
 }
 if(p->next==NULL)
  return 0;
 s->next=p->next;
 free(p);
 return 1;
}
//销毁 
void ListDestroy(LinkList *head)
{
 LinkList p,p1;
 p=*head;
 while(p!=NULL)
 {
  p1=p;
  p=p->next;
  free(p1);
 }
 *head=NULL;
}
#endif

.c文件

#include"Llist.h"
int main(void)
{
 SingleLinkedList *head,*p,*q;
 int n,i;
 ListInitialize(&head);
 p=head;
 printf("head:%p", p->next);
 //添加有序单链表的元素 ,并打印出来 
 puts("*********有序单链表***********");
 puts("有序单链表的数据元素显示:"); 
 for(i=0;i<15;i++)
 {
  printf("%d->", i+1);
  q=(SingleLinkedList *)malloc(sizeof(SingleLinkedList));
  q->data=i+1;
  q->next=p->next;
  p->next=q;
  p=q;
 }
 puts("");
 puts(""); 
 
 //插入操作
 ElemType d;
 puts("*********插入操作***********");
 printf("请输入需要插入的数据:");
 scanf("%d",&d);
 ListInsert(head,d);
 puts(""); 
 
 //删除操作
 puts("*********删除操作***********");
 printf("请输入需要删除的数据:");
 scanf("%d",&d);
 if(!(ListDelete(head,d)))
  puts("删除失败!"); 
 puts(""); 
 puts("**********打印*************"); 
 p=head;
 puts("修改后的有序单链表:");
 for(i=0;i<16;i++)
 {
  if(p->next == NULL)
   break;
  p=p->next;
  printf("%d ",p->data);
 }
 puts("");
 puts("*****************************");
retun 0;

运行结果
单链表的运行结果

4.2 双向循环链表
4.2.1 双向链表:空间换时间的设计思想
相比单链表
删除/插入操作,有两种情况:1)给定值 2)给定指向结点指针
针对1)情况,都需要从头结点遍历,时间复杂度O(n)
针对2)情况,单链表要从头结点找到要删除结点的前驱结点,时间复杂度O(n),而双链表不需要,时间复杂度O(1)
查询:双链表可记录上次查找的位置p,每次查询可与p比较大小,从而确定向前查询,还是向后。
故查询效率比单链表高些
4.2.2 双向循环链表的模型图
双向循环链表模型
以下“双向循环链表”的代码实现来自

https://mp.weixin.qq.com/s/QIQM7MxAGb5sovrtSqRC2A

用了“面向对象”和“面向工具”的思维编写。函数命名上遵循STL的标准函数命名,赞!
1)C++代码

  1#ifndef _DCLINK_H_
  2#define _DCLINK_H_
  3
  4#include<iostream>
  5#include<stdlib.h>
  6using namespace std;
  7
  8template<typename Type>
  9class DCLink;
 10
 11template<typename Type>
 12class DCLinkNode{  //这是双向循环链表的结点类型
 13    friend class DCLink<Type>;
 14public:
 15    DCLinkNode(Type x = 0){
 16        prev = next = NULL;
 17        data = x;
 18    }
 19    ~DCLinkNode(){
 20
 21    }
 22private:
 23    Type data; //数据域
 24    DCLinkNode *prev; //前驱结点指针
 25    DCLinkNode *next; //下一个结点指针
 26};
 27
 28template<typename Type>
 29class DCLink{
 30public:
 31    DCLink(){  //初始化对象的构造函数
 32        DCLinkNode<Type> *s= new DCLinkNode<Type>;
 33        first = last = s;
 34        s->next = first;
 35        s->prev = last;
 36        size = 0;
 37    }
 38    ~DCLink(){
 39
 40    }
 41public:  
 42    bool push_back(const Type &); //尾随函数
 43    void show_link()const;  //显示链表 
 44    bool push_front(const Type &);  //前插函数
 45    bool insert_val();  //根据值得插入
 46    bool pop_back();  //删除最后一个结点
 47private:
 48    bool insert_pos();
 49private:
 50    DCLinkNode<Type> *first;  //永远指向链表第一个结点
 51    DCLinkNode<Type> *last;   //永远指向链表的最后一个结点
 52    size_t size;  //统计结点个数,不算没有数据的第一个;
 53};
 54
 55template<typename Type>
 56bool DCLink<Type>::push_back(const Type &x){
 57    DCLinkNode<Type> *s = new DCLinkNode<Type>(x);
 58    if(s == NULL){
 59        return false;
 60    }
 61    s->next = first;
 62    s->prev = last;
 63    last->next = s;
 64    first->prev = s;
 65    last = s;
 66    size++;
 67
 68    return true;
 69}
 70
 71template<typename Type>
 72void DCLink<Type>::show_link()const{
 73    DCLinkNode<Type> *p = first->next;
 74
 75    while(p != first){
 76        cout<<p->data<<"--> ";
 77        p = p->next;
 78    }
 79    cout<<"NULL"<<endl;
 80}
 81
 82template<typename Type>
 83bool DCLink<Type>::push_front(const Type &x){
 84    DCLinkNode<Type> *s = new DCLinkNode<Type>(x);
 85    if(s == NULL){
 86        return false;
 87    }
 88    s->next = first->next;
 89    first->next->prev = s;
 90    s->prev = first;
 91    first->next = s;
 92    size++;
 93
 94    return true;
 95}
 96
 97template<typename Type>
 98bool DCLink<Type>::insert_val(){
 99    int number1;
100    int number2;
101
102    cout<<"请输入要插入位置的值 :";
103    cin>>number1;
104    cout<<"请输入要插入的数据";
105    cin>>number2;
106
107    DCLinkNode<Type> *p = first->next;
108    DCLinkNode<Type> *s = new DCLinkNode<Type>(number2);
109    while(p != first){
110        if(p->data == number1){
111             s->prev = p->prev;
112             s->next = p;
113             p->prev->next = s;
114             p->prev = s;
115             size++;
116        }
117        p = p->next;
118    }
119
120    return true;
121}
122
123template<typename Type>
124bool DCLink<Type>::pop_back(){
125    DCLinkNode<Type> *tmp = last->prev;
126    DCLinkNode<Type> *tmp1 = last;
127
128    tmp->next = first;
129    first->prev = tmp;
130    last = tmp;
131    size--;
132
133    delete tmp1;
134    tmp1 = NULL;
135
136    return true;
137}
138
139#endif

2)测试代码

 1#include<iostream>
 2#include"dclink.h"
 3using namespace std;
 4
 5int main(void){
 6    DCLink<int> dc;
 7
 8    for(int i = 0; i < 10; i++){
 9        dc.push_back(i);
10    }
11
12    dc.push_front(-1);
13    dc.show_link();
14    dc.insert_val();  //均是前插
15    dc.show_link();
16    dc.pop_back();
17    cout<<"删除最后一个结点时如下: "<<endl;
18    dc.show_link();
19
20    return 0;
21}
22#endif

3)测试结果
双向循环链表的测试结果图

参考资料:
1)https://mp.weixin.qq.com/s/QIQM7MxAGb5sovrtSqRC2A(推荐关注“编程剑谱”微信公众号)
2)极客时间的《数据结构与算法之美》专栏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值