数据结构1:链表

关于数据结构,可以大致做如下分类

当需要存储一系列元素时,我们可能会想到数组,但是数组增删元素,又要改变其他元素的索引,十分麻烦,这时我们就需要链表。

目录

        一、链表是什么

链表与数组:

       二、链表的组成与结构

结构:

​编辑

表示方法:

三、链表操作

1.求表长

2.查找

3.插入

4.删除

进行链表操作时的小技巧

四、单链表

例1:向链表头插入一个数(把红色的点变成第一个)

例2:删除第 k 个节点后面的数

五、双链表

例1:插入某个点

例2:删除第k个点

目录

一、链表是什么

链表与数组:

二、链表的组成与结构

结构:

​编辑

表示方法:

三、链表操作

1.求表长

2.查找

3.插入

4.删除

进行链表操作时的小技巧

四、单链表

例1:向链表头插入一个数(把红色的点变成第一个)

例2:删除第 k 个节点后面的数

五、双链表

例1:插入某个点

例2:删除第k个点

六、模板


一、链表是什么

链表是由节点和指针组成的数据结构,每个节点存有一定的值,和一个指向下一个节点的指针,因此很多链表问题可以用递归来处理。

链表就像寻宝游戏,需要读取链表的最后一个元素时,不能直接读取,因为不知道它所处的地址,必须先访问元素1,从而获取元素2的地址,再访问元素2并从中获取元素3的地址。以此类推,直到访问最后一个元素。

链表与数组:

由于链表中的元素存储在内存的各个地方,所以链表擅长插入和删除

同时链表必须读取所有元素,当需要随机读取元素时,应选择数组。.

链表和数组关系十分密切,但在平常中,往往是数组用的更多。

二、链表的组成与结构

结构:

head为 头指针 ,

最后一定会指向空集,空集的指针表示为-1,即 ne[n]=-1。

表示方法:

struct node
{
   int val;
   struct node * next;
};

三、链表操作

1.求表长

采用链表遍历的方法来求

先设一个临时的指针p,指向链表的头(ptrl),把计数器 j 一开始设为0,然后开始遍历,只要p没有指向null,循环就不会结束

int length (list ptrl)
{
    list p=ptrl;  //p指向表的第一个节点
    int j=0;
    while(p){
        p = p->next;
        j++;      //当前p指向的是第j个节点
    }
    return j;
}

2.查找

仍然采用遍历的方法

先把临时的指针变量p设为表头(ptrl),用 i 表示第几个元素,接下来只要p没有指向null,并且还没有找到第k个元素,循环就不会结束,直到找到第k个元素

list FindKth( int k,list ptrl)
(
    list p = ptrl;
    int i=1;
    while(p!=null && i<k){
         p=p->next;
         i++;
     }
     if(i==k) return p;  //找到第k个,返回指针
     else return null;
}

3.插入

在第 i-1个节点后插入值为x的新节点

链表的插入,一定要知道插的节点的前面一个是是谁

具体步骤:(动态插入)

先用malloc这个函数申请一块空间,构造一个新节点,用s指向

再找到链表的第 i-1个节点,用p指向

修改指针,插入节点(也就是说,在p后插入s)

list Insert( ElementType x, int i, list ptrl)
{
    list p,s;
    if(i==1){
      s = (list)malloc (sizeof(struct LNode)); //让新节点插在表头
      s->data =x;
      s->next =ptrl;
      return s;   //返回新表头指针
    }
    p= FindKth( i-1,ptrl);   //查找第i-1个节点

    if(p == null){
       cin>>"参数i出错";
       return null;
    }
    else{
       s = (list)malloc (sizeof(struct LNode)); //申请,换装节点
       s->data =x;
       s->next =p->next;  //新节点插在第i-1个节点后面
       p->next =s;
       return ptrl;
    }
}

4.删除

删除链表的第 i 个节点

(要把第 i 个节点删除,前面一个节点就要接到后面去)

具体步骤

先找到链表的第 i-1个节点,用p指向

再用指针s指向要被删除的节点(p的下一个节点)

然后修改指针,删除s所指节点

最后通过free函数释放所指节点的空间

list Delete(int i, list ptrl)
{
    list p,s;
    
    if(i==1){       //如果要删除第一个节点
       s=ptrl;      //s指向第一个节点
       if(ptrl != null) ptrl=ptrl->next;   //从链表中删除
       else  return null;
       free(s);
       return ptrl;
    }

    p=FindKth( i-1,ptrl);    //查找第i-1个节点
    if(p==null){
       cout<<"第i-1个节点不存在“;
       return null;
    }
    else if(p->next==null){
       cout<<"第i个节点不存在";
    }
    else{
      s=p->next;            //s指向第i个节点
      p->next = s->next;    //从链表中删除
      free(s);              //释放被删除节点
      return ptrl;
    }
}

进行链表操作时的小技巧

在进行链表操作时,尤其是删除节点时,经常会因为对当前节点进行操作,而导致内存或指针出现问题

有两个小技巧可以解决这个问题:

一是尽量处理当前节点的下一个节点,而非当前节点本身;二是建立一个虚拟节点(dummy node),使其指向当前链表的头节点,这样,即使原链表所有节点全部删除,也会有一个dummy存在,返回dummy->即可。

四、单链表

其中上述e[ ],ne[ ]这种用数组模拟链表的叫做 静态链表,这种速度较快,比较常用

最大的用途是可以来写邻接表,而邻接表最常用的是存储图和树

例1:向链表头插入一个数(把红色的点变成第一个)

#include<iostream>
using namespace std;
const int n=101100;

//head表示头节点的下标
//e[i]表示节点i的值
//ne[i]表示节点i的next指针是多少
//idx 存储当前已经用到了哪个点

int  head, e[n], ne[n], idx;

//初始化
void init()
{
   head=-1;
   idx=0;
}

//将x插到头节点    //常用!!!!
void add_to_head(int x)
{
   e[idx]=x;
   ne[idx]=head;  //①操作
   head=idx;      //②操作
   idx++;
}

例2:删除第 k 个节点后面的数

#include<iostream>
using namespace std;
const int n=101100;

//head表示头节点的下标
//e[i]表示节点i的值
//ne[i]表示节点i的next指针是多少
//idx 存储当前已经用到了哪个点

int  head, e[n], ne[n], idx;

//初始化(任何时候都别忘了)
void init()
{
   head=-1;
   idx=0;
}

void remove(int k)
{
   ne[k] = ne[ne[k]];  //①操作
}

五、双链表

每个节点有两个指针,一个指向前,另一个指向后。通常用 l[n]表示左边指向的谁,r[n]表示右边指向的谁。

在本文规定下标为0表示head,下标为1表示最后一个点。

双链表常用来优化某些问题

例1:插入某个点

#include<iostream>
using namespace std;
const int n=101100;

//e[i]表示节点i的值是多少
//idx 存储当前已经用到了哪个点

int e[n], l[n], r[n], idx;

//初始化
void init()
{
   //0表示左端点,1表示右端点
   r[0]=1, l[1]=0;
   idx=2;
}

//在下标为k的点的右边,插入x
void add(int k, int x)
{
   e[idx]=x;    //先赋值
   r[idx]=r[k];  //①操作
   l[idx]=k;     //②操作
   l[r[k]]=idx;  //③操作
   r[k]=idx;     //④操作
}

//在下标为k的点的左边,插入x,调用上面的add函数
add(l[k],x);

例2:删除第k个点

#include<iostream>
using namespace std;
const int n=101100;

//e[i]表示节点i的值是多少
//idx 存储当前已经用到了哪个点

int e[n], l[n], r[n], idx;

//初始化
void init()
{
   //0表示左端点,1表示右端点
   r[0]=1, l[1]=0;
   idx=2;
}

//删掉第k个点
void remove(int k)
{
   r[l[k]]=r[k];   //①操作
   l[r[k]]=l[k];   //②操作
}

六、模板

单链表

// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 在链表头插入一个数a
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
    head = ne[head];
}

双链表

// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 在链表头插入一个数a
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
    head = ne[head];
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值