数据结构与算法:链表——03

四、链表

1、链表概述

什么是链表?

  • 链表和数组一样同属于线性表,其特点为不需要逻辑上相邻的元素在物理位置上也相邻,即在不连续的地址中存储一系列数据 ,所以链表不能向数组一样通过下标随机访问。(简述:链表逻辑结构上相邻,物理结构【内存碎片化】上下不相邻

那么链表之间的元素又是如何相互关联的呢?

  • 它是由一系列的存储数据元素的单元通过指针串接来确定元素之间相互关系的,因此每个单元至少都有两个域—数据域和指针域,这样的单元也被称为节点(node)。

链表分类:

  • 1.单链表(最普通的链表)
  • 2.双向链表(有两个指针域)
  • 3.循环链表(首尾相接)
  • 4.静态链表(链表的数组表示法)

链表的优缺点:

  • 优点:
    • (1)插入和删除速度快,保留原有的物理顺序,在插入或者删除一个元素的时候,只需要改变指针指向即可。
    • (2)没有空间限制,存储元素无上限,只与内存空间大小有关。
    • (3)动态分配内存空间,不用事先开辟内
  • 缺点:
    • (1)占用额外的空间以存储指针,比较浪费空间,不连续存储,空间碎片比较多)
    • (2) 查找速度比较慢,因为在查找时,只能顺序查找,需要循环链表

2、单链表

问题引入:操作系统内存分配

  • 定义:单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。

  • 视图分析:

  • 在这里插入图片描述

  • java代码概要内容:

    • class Data       //存放数据域相关信息
      class Node{      //节点:包含数据域和指针
          public Data data;//数据域
          public Node next;//指针域
      }     
      class List{
           //添加元素
          public void add(Node node){}
          //通过id删除元素
          public void delete(int id){}
          //显示链表,进行遍历
          public void view(){}
          //向指定位置插入元素(默认为指定位置的后面)
          public void insert(int id,Node node){}
          //修改指定节点的的名字,默认通过id查找
          public void modify(int id,String name){
          
      }
      
    • 具体实现如下:

  • 链表:

    • //数据域的类
      class Data{
          int id;//编号,注:每个人对应的ID是唯一的
          String name;//名称
      
          public Data(int id, String name) {
              this.id = id;
              this.name = name;
          }
      
          @Override
          public String toString() {
              return "Data{" +
                      "id=" + id +
                      ", name='" + name + '\'' +
                      '}';
          }
      }
      //节点类:包含数据域和指针类
      class Node{
          public Data data;//数据域
          public Node next;//指针域
          //创建构造方法
          public Node(Data data) {
              this.data = data;
          }
      
          @Override
          public String toString() {
              return data.toString();
          }
      }
      //创建链表类
      class List{
          //初始化头结点
          private Node head=new Node(new Data(0,""));
          //添加元素
          public void add(Node node){
              //辅助变量
              Node temp =head;
              //1、进行尾插法,那么如何判断这是最后一个节点
              while (true){
                  //如果一个节点指针是空的,说明这是链表的表尾。
                  if (temp.next==null){
                      break;
                  }
                  temp=temp.next;//这里进行遍历后移指针,这里的指针域是一个节点对象,在这里是指向下一个节点
              }
              //这里就需要将新添加的节点加入到temp的下一个节点,temp不是空的,因为我们判断的temp的后一个位置是否为空。
              temp.next=node;
          }
          //通过id删除元素
          public void delete(int id){
              //辅助变量
              Node temp=head;
              while (true){
                  if (temp.next==null){
                      //说明到达链表尾,循环结束
                      break;
                  }
                  //从头节点开始遍历,这里表示:temp的下一个节点的数据域的id与我们要删除的节点是相同的,这说明找到要删除的节点
                  if (temp.next.data.id==id){
                      break;
                  }
                  temp=temp.next;
              }
              //注意:我们找到的是temp的后一个节点与我们要删除的ID相符合的节点,因此我们需要将temp的指针指向要删除的的节点的下一个节点
              temp.next=temp.next.next;
              //理解:temp是要删除的前一个节点   temp.next是temp的种指针指向要删除的节点   temp.next.next是要删除的节点的指针指向后一个节点
          }
          //向指定位置插入元素(默认为指定位置的后面)
          public void insert(int id,Node node){
              //辅助变量
              Node temp =head.next;
              //同理判断插入位置id是否一致
              while (true){
                  if (temp==null){
                      break;
                  }
                  if (temp.data.id==id){
                      break;
                  }
                  temp=temp.next;
              }
              //这里找到要插入的位置temp节点后,那么我们要先保存原链表的temp后一个节点
              Node cache =temp.next;
              //然后将插入节点插入temp后,就是temp的指针指向新的插入节点
              temp.next=node;
              node.next=cache;
          }
          //显示链表,进行遍历
          public void view(){
              //默认输出表头
              System.out.println(head.toString());
              //判断到达链表最后,如果没有到链表最后,我们进行输出,还是需要辅助节点[这种情况包括了链表为空或者已到达链表表尾]
              Node temp =head;
              while (true){
                  if (temp.next==null){
                      break;
                  }
                  temp=temp.next;
                  System.out.println(temp.toString());
              }
          }
          //修改指定节点的的名字,默认通过id查找
          public void modify(int id,String name){
              //直接指向头结点的下一个位置,便于比对
              Node temp = head.next;
              while (true){
                  if (temp==null){
                      //判断链表是否为空
                      break;
                  }
                  //判断该节点是否ID于修改的id是相同的,如果相同,退出循环
                  if (temp.data.id==id){
                      break;
                  }
                  temp=temp.next;
              }
              //循环退出后执行修改名字
              temp.data.name=name;
          }
      }
      
      
  • 测试:

    • package 算法.数组.链表;
      
      public class ListDemo01 {
          public static void main(String[] args) {
              //1、先创建节点
              Node node = new Node(new Data(1,"张继豪"));
              Node node1= new Node(new Data(2,"沈峰"));
              Node node2= new Node(new Data(3,"耿峰素"));
              Node node3= new Node(new Data(4,"欧阳丰"));
              //2、创建链表
              List list = new List();
              list.add(node);
              list.add(node1);
              list.add(node2);
              list.add(node3);
      
              //3、显示
              System.out.println("查看:");
              list.view();
              //4、删除指定位置
              System.out.println("====================");
              System.out.println("删除指定节点后:");
              list.delete(1);
              list.delete(4);
              list.view();
              //5、修改指定位置节点
              list.modify(2,"王大为");
              System.out.println("====================");
              System.out.println("修改指定位置节点数据域:");
              list.view();
              //向指定位置插入节点
              Node node4 = new Node(new Data(17,"东风苏"));
              list.insert(3,node4);
              System.out.println("====================");
              System.out.println("向指定位置插入后链表:");
              list.view();
      
          }
      }
      
    • 结果展示:在这里插入图片描述

  • C语言展示:

    • 代码展示:
    • /*
       *文件名:ListDemo01.c
       *作者  :张水图(解释权归作者所有)
       *日期  : 2023/2/19
       *功能  :单链表相关功能实现
       */
      //头文件导入
      #include<stdio.h>
      #include<math.h>
      #include<string.h>
      #include<stdlib.h>
      #include<stdbool.h>
      
      //数据域
      typedef struct student{
          int id;
          char* name;
      }stu;
      //构成节点类
      typedef struct Node{
          //单链表的节点有数据域和后继指针组成
          struct student st;
          //后继指针
          struct Node* next;
      }Node;
      //链表头结点,即指向链表的首地址即可
      struct Node* head;
      //初始化头结点,链表一旦初始化头结点,既可以需要进行链表相关其余操作
      struct Node* init(){
          head=(Node*) malloc(sizeof(head));
          if(head==NULL){
              printf("堆空间申请失败");
              exit(0);
          }
          head->st.id=0;
          head->st.name=NULL;
          head->next=NULL;
          return head;
      }
      //创建的节点的堆空间申请
      struct Node* apply(Node* node){
          node=(Node*) malloc(sizeof (node));
          if(node==NULL){
              printf("堆空间申请失败");
              exit(0);
          }
          node->st.id=0;
          node->st.name=NULL;
          node->next=NULL;
      
          return node;
      }
      
      //函数声明
      static void add();
      static void insert(int id);
      static void del(int id);
      static void select(int id);
      static void getall();
      static void modify(int id);
      static Node* creat();
      int main(void) {
          //链表初始化
          head=init();
          char key;
          bool flag=true;
          int id;
          while (flag){
              //刷新缓存
              fflush(stdin);
              printf("请输入字母进行链表操作:添加(a),删除(d),插入(i),查询(s),遍历(l),修改(m),程序终止(e)\n");
              scanf("%c",&key);
              switch (key) {
                  case 'a':
                      add();
                      break;
                  case 'd':
                      printf("请输入你需要删除指定位置id:");
                      scanf("%d",&id);
                      del(id);
                      break;
                  case 'i':
                      printf("请输入你需要插入指定位置id:");
                      scanf("%d",&id);
                      insert(id);
                      break;
                  case 's':
                      printf("请输入你需要查询指定位置id:");
                      scanf("%d",&id);
                      select(id);
                      break;
                  case 'l':
                      getall();
                      break;
                  case 'm':
                      printf("请输入你需要修改指定位置id:");
                      scanf("%d",&id);
                      modify(id);
                      break;
                  case 'e':
                      flag=false;
                      printf("程序终止~");
                      break;
              }
          }
          return 0;
      }
      //1.1、单链表添加功能(尾插法)
      static void add(){
          Node* node=creat();
          //创建辅助节点
          Node* temp=head;
          while (temp->next!=NULL){
              temp= temp->next;
          }
          temp->next=node;
      }
      //1.2.根据链表id指定插入位置(默认插入位置为后)。
      static void insert(int id){
          Node* node =creat();
          Node* temp=head;
          while (true){
              if(temp->st.id==id){
                  Node* variable=temp->next;
                  temp->next=node;
                  node->next=variable;
                  break;
              }
              if(temp->next==NULL){
                  printf("当前链表未含有指定id,因此无法插入");
                  break;
              }
              temp=temp->next;
          }
      }
      //2、单链表节点删除指定节点((根据对应id))
      static void del(int id){
          Node* temp=head;
          while (true){
              if(temp->next->st.id==id){
                  temp->next=temp->next->next;
                  break;
              }
              if(temp->next==NULL){
                  printf("当前链表未含有指定id或删除节点为头结点,因此无法删除\n");
                  break;
              }
              temp=temp->next;
          }
      }
      //3、查询指定节点数据(根据对应id)
      static void select(int id){
          Node* temp=head;
          while (true){
              if(temp->st.id==id){
                  printf("id:%d,name:%s\n",temp->st.id,temp->st.name);
                  break;
              }
              if(temp->next==NULL){
                  break;
              }
              temp=temp->next;
          }
      }
      //4、遍历单链表
      static void getall(){
          if(head->next==NULL){
              printf("当前链表为空,无法获取具体值");
              return;
          }
          Node* temp=head;
          while (temp!=NULL){
              printf("id:%d,name:%s\n",temp->st.id,temp->st.name);
              temp=temp->next;
          }
      }
      //5、修改单链表
      static void modify(int id){
          Node* temp=head;
          while (true){
              if(temp->st.id==id){
                  int id;
                  printf("请输入你需要修改的id:");
                  scanf("%d",&id);
                  char* name=(char*) malloc(sizeof(char*));
                  printf("\n请输入你需要修改的name:");
                  scanf("%s",name);
                  temp->st.id=id;
                  temp->st.name=name;
              }
              if(temp->next==NULL){
                  break;
              }
              temp=temp->next;
          }
      }
      //函数内节点的创建多次调用
      static Node* creat(){
          //创建节点
          Node* node;
          //为节点分配堆空间
          node= apply(node);
          printf("请输入你需要添加的节点id:");
          scanf("%d",&node->st.id);
          char* name=(char*) malloc(sizeof(char*));
          printf("\n请你输入你需要添加的节点name:");
          scanf("%s",name);
          node->st.name=name;
      
          return node;
      }
      
    • 实现效果:在这里插入图片描述

3、单链表面试题(这里不过多叙述C语言编写形式,如果想实现,可自行实现)

  • 依据上述单链表代码编写方法

求单链表节点的个数:

    //求一个链表的节点个数:不包含头结点
    public int CountNode(){
        int count=0;
        Node temp =head;
        while (true){
        if (temp.next==null){
                //说明到达链表表尾
                break;
            }
            temp=temp.next;
            count++;
        }
        return count;
    }

单链表的反转:

(1):反向遍历:思路,创减一个新的链表,将之前的链表进行头插法插入新的链表中,让就的链表表头指向新的链表表头的下一个节点

(2):Stack栈

  • (1):反向遍历:

    •  //反向遍历
          public void GetReverse(Node head){
              if (head.next==null||head.next.next==null){
                  return ;
              }
              //创建一个新的头结点,获得一个新的链表
              Node head1 = new Node(new Data(0,""));
              //创建辅助节点,先遍历获取旧的链表的节点
              Node temp=head.next;
              //缓存头结点的下一个节点
              Node cur=null;
              while(temp!=null){
                  //头插法:需要先保存旧的链表temp的下一个节点
                  cur=temp.next;
                  //这里是指temp的指针指向head1的头结点的下一个节点
                  temp.next=head1.next;
                  head1.next=temp;
                  temp=cur;//这个是指针后移
              }
              //最后让旧链表的头结点执行新的头结点的下一个位置
              head.next=head1.next;
          }
      
  • (2):Stack栈:

    •    //反向遍历2:不改变原链表的位置节点
          public void getReverse2(){
              Stack stack = new Stack();
              Node temp=head.next;
              while (true){
                  if (temp==null){
                      break;
                  }
                  stack.push(temp);
                  temp=temp.next;
              }
              while (stack.size()>0){
                  System.out.println(stack.pop());
              }
          }
      

4、双向链表

双向链表:

  • 定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

视图分析:在这里插入图片描述

  • 思路解析:

    • 为什么初始化就含有头结点和尾节点?

      • 双链表一旦初始化,头结点和尾节点能帮助我们迅速遍历双链表,无论是从头结点开始还是从尾节点开始,能快速获取链表各个节点。
    • 上述图解分析了向指定位置插入和删除,那么在链表刚开始初始化时,我们首尾节点并没有相连,我们又是如何将一个个节点添加进去的?

      • 创建一个计数属性count;每添加一个节点就count++;

      • 创建辅助节点temp=head,并进行循环,只要temp.next!=null;就进行temp=temp.next;

      • 让尾节点等于要添加的节点,并进行连接。

      • tail=node;
        temp.next=tail;
        tail.pre=temp;
        count++;
        
    • 注意事项:每删除一个节点或添加一个节点我们都要刷新尾节点。(这里考虑是否为头插法或者尾插法,具体因素自行考虑)

  • 测试:

    • 链表:

      //创建双向链表
      //节点数据类
      class Data2{
          public int id;
          public String name;
      
          public Data2(int id, String name) {
              this.id = id;
              this.name = name;
          }
          public Data2(){
      
          }
          @Override
          public String toString() {
              return "Data2{" +
                  "id=" + id +
                  ", name='" + name + '\'' +
                  '}';
          }
      }
      //节点类:包含数据域跟前继指针和后继指针
      class Node2{
          public Node2 pre;//前继指针
          public Data2 data2;//数据域
          public Node2 next;//后继指针
      
          public Node2(Data2 data2) {
              this.data2 = data2;
          }
          public Node2(){
      
          }
      
          @Override
          public String toString() {
              return data2.toString();
          }
      }
      
      //创建链表及其操作链表的方法
      class List2{
          public  static Node2 head=new Node2(new Data2(0,null));
          public Node2 tail =new Node2();
          public int count=0;
          //获取头结点
          public Node2 getHead() {
              return head;
          }
          //添加链表节点:这里是尾插法
          public void add(Node2 node2){
              Node2 temp=head;
              while (true){
                  if (temp.next==null){
                      break;
                  }
                  temp=temp.next;
              }
              tail=node2;
              temp.next=tail;
              tail.pre=temp;
              count++;
              //这里是一直刷新尾节点
              Node2 tailnode = tailnode();
              tail=tailnode;
          }
          //在指定id位置添加链表节点(默认添加在指定ID的后面)
          public void insert(int id,Node2 node2) {
              Node2 temp = head;
              int temporary=0;
              boolean flag=true;
              while (true) {
                  if (temp == null) {
                      if (temporary==0){
                          System.out.println("链表为空");
                      }
                      if (flag){
                          System.out.println("不存在对应添加指定位置的节点");
                      }
                      break;
                  }
                  //如果插入的位置是最后一个节点,我们需要判断,因为最后一个的下一个节点为空
                  if (temp.data2.id == id) {
                      if (temp.next==null){
                          temp.next = node2;
                          node2.pre = temp;
                      }else {
                          //先要保存指定id节点的下一个节点
                          Node2 po = temp.next;
                          //如果不保存temp.next,就会发生如下代码造成数据修改,逻辑错误
                          temp.next = node2;
                          node2.pre = temp;
                          node2.next = po;
                          po.pre = node2;
                      }
                      flag=false;
                  }
                  temp=temp.next;
                  temporary++;
              }
              //一样需要刷新尾节点
              Node2 tailnode = tailnode();
              tail=tailnode;
          }
          //删除链表节点(通过指定ID进行删除)
          public void del(int id){
              //辅助节点
              //这里是先判断
              int temporary =0;
              Node2 temp =head;
              while (true){
                  //同理,这里判断指定ID是否对应存在,如果不存在则不存在删除节点,同时判断链表是否为空
                  if (temp.next==null){
                      if (temporary==0){
                          System.out.println("链表为空");
                      }
                      if (temporary>count){
                          System.out.println("不存在对应删除节点");
                      }
                      break;
                  }
                  if (temp.data2.id==id){
                      break;
                  }
                  temp=temp.next;
                  temporary++;
              }
              //这句话什么意思?:思考,我们需要将temp的前一个节点的后继指针指向temp的后一个节点
              temp.pre.next=temp.next;
              /*temp的后一个节点的前继指针指向temp的前一个节点,但是这里有一个问题,
              *如果我们要删除的是最后一个节点,temp.next=null,在前一句代码是没有问题的,但是后一句temp.next.pre=null.pre,
              *说明节点本身就是不存在的,会出现空指针异常,因此我们需要判断
              */
              if (temp.next!=null){
                  temp.next.pre=temp.next;
              }
              //刷新尾节点
              Node2 tailnode = tailnode();
              tail=tailnode;
          }
          //遍历链表节点
          public void getall(){
              //这里我们从头开始遍历
              Node2 temp=head;
              while (true){
                  if (temp.next==null){
                      break;
                  }
                  temp=temp.next;
                  //包含头结点和尾节点
                  System.out.println(temp.toString());
              }
          }
          //通过id查找指定的链表
          public void specified(int id){
              //从头找
              Node2 temp=head;
              while (true){
                  if (temp.next==null){
                      break;
                  }
                  if (temp.data2.id==id){
                      System.out.println(temp.toString());
                      break;
                  }
                  temp=temp.next;
              }
          }
      
          //通过id修改链表的节点数据域
          public void modify(int id,Data2 data2){
              //从头找
              Node2 temp=head;
              while (true){
                  if (temp==null){
                      break;
                  }
                  if (temp.data2.id==id){
                      temp.data2=data2;
                      break;
                  }
                  temp=temp.next;
              }
              //如果修改的为尾节点,我们同时需要刷新尾节点
              if (temp.next==null){
                  Node2 tailnode = tailnode();
                  tail=tailnode;
              }
          }
          //获取尾节点并赋值到tail尾节点,因为我们每进行一次增删改,尾节点就会发生变化,这样才能满足我们不论从后面遍历还是从前面遍历,他都可以获得双链表。
          public static Node2 tailnode(){
              //这里我们从头开始遍历
              Node2 temp=head;
              while (true){
                  if (temp.next==null){
                      break;
                  }
                  temp=temp.next;
                  //包含头结点和尾节点
              }
              return temp;
          }
      
      }
      
    • 测试实现:

      package 算法.数组.链表;
      
      public class ListDemo02 {
          public static void main(String[] args) {
              //测试:先创建节点
              Node2 node =  new Node2(new Data2(1,"我的三个明天"));
              Node2 node1 = new Node2(new Data2(2,"格列佛游记"));
              Node2 node2 = new Node2(new Data2(3,"十万个为什么"));
              Node2 node3 = new Node2(new Data2(4,"钢铁是怎样炼成的"));
              Node2 node4 = new Node2(new Data2(5,"梅花园的日常"));
              Node2 node5 = new Node2(new Data2(6,"毛泽东思想"));
      
              //创建链表并初始化最大容量
              List2 list2 = new List2();
              //1.测试添加
              list2.add(node);
              list2.add(node1);
              list2.add(node2);
              list2.add(node4);
              list2.add(node5);
              list2.add(node3);
              //2.遍历链表
              System.out.println("遍历全部节点:");
              list2.getall();
              System.out.println("===========================");
      
              //3.测试删除指定节点
              System.out.println("删除指定节点:");
              list2.del(4);
              list2.getall();
              //4.查询尾节点
              System.out.println("===========================");
              System.out.println("查询尾节点:");
              System.out.println(list2.tail.toString());
              System.out.println(list2.tail.pre.toString());
              //5.修改节点数据域
              System.out.println("===========================");
              System.out.println("修改节点数据域:");
              list2.modify(6,new Data2(45,"毛泽东思想"));
              list2.getall();
              System.out.println("===========================");
              //6.再次查询从尾部遍历节点
              System.out.println("再次查询尾节点:");
              System.out.println(list2.tail.toString());
              System.out.println(list2.tail.pre.toString());
              //7通过id查找指定链表
              System.out.println("查找指定节点【通过位移id寻找】:");
              list2.specified(5);
              System.out.println("============================");
              //8.通过id插入指定位置
              list2.insert(45,new Node2(new Data2(4,"余生")));
              System.out.println("插入指定位置后遍历得:");
              list2.getall();
      
          }
      }
      
  • 结果:在这里插入图片描述

  • C语言实现:

    • 代码展示:
    • /*
       *文件名:ListDemo02.c
       *作者  :张水图(解释权归作者所有)
       *日期  : 2023/2/21
       *功能  :讲解双向链表
       */
      //头文件导入
      #include<stdio.h>
      #include<math.h>
      #include<string.h>
      #include<stdlib.h>
      #include<stdbool.h>
      
      //数据域
      struct Data{
          int id;
          char* name;
      };
      //节点域
      typedef struct Node{
          //前驱指针
          struct Node* pre;
          //数据域
          struct Data data;
          //后继指针
          struct Node* next;
      }Node;
      //声明全局变量:双向链表用该包含头节点和尾节点
      static Node* head;
      static Node* tail;
      //链表初始化
      void init(){
          //为链表头结点和尾节点分配堆空间
          head=(Node*)malloc(sizeof (Node));
          tail=(Node*)malloc(sizeof(Node));
          if(head==NULL||tail==NULL){
              printf("堆空间申请失败");
              exit(0);
          }
          head->data.id=0;
          head->data.name=NULL;
          tail=NULL;
          head->next=NULL;
      }
      //函数声明
      static Node* create();
      static void add();
      static void  del(int id);
      static void insert(int id);
      static void getall();
      static void getone(int id);
      static Node* flushtail();
      static void modify(int id);
      int main(void) {
          //双向链表初始化
          init();
          char key;
          int id;
          bool flag=true;
          while (flag){
              fflush(stdin);
              printf("请输入字母进行链表操作:添加(a),删除(d),插入(i),查询(s),遍历(l),修改(m),尾节点获取(t),程序终止(e)\n");
              scanf("%c",&key);
              switch (key) {
                  case 'a':
                      add();
                      break;
                  case 'd':
                      printf("请输入你要删除节点的id:");
                      scanf("%d",&id);
                      del(id);
                      break;
                  case 'i':
                      printf("请输入你要插入位置节点后的id:");
                      scanf("%d",&id);
                      insert(id);
                      break;
                  case 's':
                      printf("请输入你要查询节点的id:");
                      scanf("%d",&id);
                      getone(id);
                      break;
                  case 'l':
                      getall();
                      break;
                  case 'm':
                      printf("请输入你要修改节点的原id:");
                      scanf("%d",&id);
                      modify(id);
                      break;
                  case 't':
                      tail=flushtail();
                      if(tail!=NULL){
                          printf("尾节点---id:%d,name:%s\n",tail->data.id,tail->data.name);
                      }
                      break;
                  case 'e':
                      flag=false;
                      break;
              }
          }
          system("pause");
          return 0;
      }
      //1、添加节点至双向链表
      static void add(){
          //构造添加节点
          Node* node =create();
          Node* variable=head;
          //一直遍历到尾节点
          while (variable->next!=NULL){
              variable=variable->next;
          }
          variable->next=node;
          node->pre=variable;
      
          tail=flushtail();
      }
      //2、删除指定节点(根据id)
      static void  del(int id){
          if(head->next==NULL){
              printf("当前链表为空,无法删除指定id对应元素\n");
              return;
          }
          Node* variable=head->next;
          while (true){
              if(variable->data.id==id){
                  //那么这时需要判断variable是否是尾节点
                  variable->pre->next=variable->next;
                  if(variable->next!=NULL){
                      variable->next->pre=variable->pre;
                  }
                  break;
              }
              if(variable->next==NULL){
                  printf("当前链表未查找到指定删除节点位置\n");
                  break;
              }
              variable=variable->next;
          }
          //刷新尾节点
          tail= flushtail();
      }
      //3、插入到指定节点位置(根据id)
      static void insert(int id){
          Node* node=create();
          if(head->next==NULL){
              printf("当前链表为空,无法插入指定节点位置\n");
              return;
          }
          Node* temp=head;
          while (true){
              if (temp->data.id==id){
                  if(temp->next==NULL){
                      temp->next=node;
                      node->pre=temp;
                  } else{
                      Node* varible=temp->next;
                      temp->next=node;
                      node->next=varible;
                      varible->pre=node;
                      node->pre=temp;
                  }
                  break;
              }
              if(temp->next==NULL){
                  printf("当前链表未查找到指定插入位置id\n");
                  break;
              }
              temp=temp->next;
          }
          tail=flushtail();
      }
      //4、获取链表所有元素
      static void getall(){
          if(head->next==NULL){
              printf("当前链表节点为空,无法获取链表所有元素\n");
              return;
          }
          Node* temp=head;
          while (temp!=NULL){
              printf("id:%d,name:%s\n",temp->data.id,temp->data.name);
              temp=temp->next;
          }
      }
      //5、获取指定ID的节点信息
      static void getone(int id){
          if(head->next==NULL){
              printf("当前链表为空,无法获取指定id对应元素\n");
              return;
          }
          Node* temp=head;
          Node* variable=head->next;
          while (true){
              if(variable->data.id==id){
                  printf("查询节点信息---id:%d,name:%s\n",variable->data.id,variable->data.name);
                  break;
              }
              if(variable->next==NULL){
                  printf("当前链表为查找到指定删除位置id\n");
                  break;
              }
              variable=variable->next;
          }
      }
      //6、更新尾节点
      static Node* flushtail(){
          if(head->next==NULL){
              printf("当前链不含有尾节点,因为为添加元素,无法获取\n");
              return NULL;
          }
          Node* temp=head;
          while (temp->next!=NULL){
              temp=temp->next;
          }
          return temp;
      }
      //7.修改节点信息
      static void modify(int id){
          if(head->next==NULL){
              printf("当前链表为空,无法插入指定节点位置\n");
              return;
          }
          Node* node=create();
          Node* temp=head;
          while (true){
              if(temp->data.id==id){
                  if(temp->next==NULL){
                      temp->pre->next=node;
                      node->pre=temp->pre;
                  } else{
                      Node* varible=temp->next;
                      temp->pre->next=node;
                      node->next=varible;
                      varible->pre=node;
                      node->pre=temp->pre;
                  }
                  break;
              }
              if(temp->next==NULL){
                  printf("当前链表未查找到指定修改位置id\n");
                  break;
              }
              temp=temp->next;
          }
      }
      //构造节点
      static Node* create(){
          Node* node;
          //为节点分配堆空间
          node=(Node*) malloc(sizeof (Node));
          if(node==NULL){
              printf("当前节点空间申请失败");
              exit(0);
          }
          node->next=NULL;
          node->pre=NULL;
          printf("请输入你需要生成节点的信息ID:");
          scanf("%d",&node->data.id);
          fflush(stdin);
          node->data.name=(char*) malloc(sizeof(char)*10);
          printf("请输入你需要生成节点的信息name:");
          scanf("%s",node->data.name);
          return node;
      }
      
      

    在这里插入图片描述

  • 结果展示:
    在这里插入图片描述

    • 实验结果展示:

5、约瑟夫问题【丢手帕】

问题引入:设有编号为1,2,……,n的n(n>0)个人围成一个圈,从第1个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。当任意给定n和m后,设计算法求n个人出圈的次序。

  • package 算法.数组.链表;
    //约瑟夫问题--丢手帕【环形单链表】
    public class CircleListDemo03 {
        public static void main(String[] args) {
            List3 list3 = new List3(15);
            list3.josepfu(3,3);
        }
    }
    //节点域
    class Node3{
        public int id;
        public Node3 next;
        public Node3(int id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "Node3{" +
                    "id=" + id +
                    "}";
        }
    }
    //链表
    class List3{
        //计数循环次数,在后续约瑟夫问题中会用到,初始化了头结点,默认为1。
        public static int cur =1;
        public Node3 node3;
        //标记头结点
        public static Node3 head=null;
        //初始化容量和现有的链表节点个数;
        public static int count;
        //初始化链表的最大容量
        public List3(int count){
            if (count<=0||count>100){
                System.out.println("超过内存限制,输入数值超过100或为负数");
            }
            this.count=count;
            for (int i = 1; i <= count; i++) {
                //提高作用域
                node3 = new Node3(i);
                if (node3.id==1){
                    head=node3;
                } else {
                    Node3 temp=head;
                    for (int j = 0; j <cur -1; j++) {
                        temp=temp.next;
                    }
                    temp.next=node3;
                    node3.next=head;
                    cur++;
                }
            }
        }
    //获取链表
        public void getall(){
            Node3 temp=head;
            for (int i = count; i >=1 ; i--) {
                System.out.println(temp.toString());
                temp=temp.next;
            }
        }
        //根据对应ID删除节点
        public static void del(int id){
            Node3 temp=head;
            for (int i = cur; i >0; i--) {
                //找到并修改后循环进行停止
                if (temp.next.id==id){
                    if (temp.next.id==1){
                        System.out.print(temp.next.id+" ");
                        temp.next=temp.next.next;
                        head=temp.next;
                        count--;
                    }else {
                        System.out.print(temp.next.id+" ");
                        temp.next=temp.next.next;
                        count--;
                    }
                    break;
                }
                temp=temp.next;
            }
        }
    
        //约瑟夫处理方法:从第n个数着走,第k个取出
        public void josepfu(int starno,int endno){
            if (head==null||starno<=0||starno>count){
                System.out.println("输入参数有误");
            }
            Node3 temp=head;
            while (true){
                if (temp.id==starno){
                    break;
                }
                temp=temp.next;
            }
            for (int i=cur;i>0;i--){
                for (int j = 1; j < endno; j++) {
                    temp = temp.next;
                }
                del(temp.id);
                //从删除后面的下一个节点报数
                temp = temp.next;
            }
        }
        //得到最后一个节点
        public  Node3 getlast(){
            Node3 temp=head;
            for (int i=count-1;i>0;i--){
                temp=temp.next;
            }
            return temp;
        }
    }
    
    
    
  • 结果展示:在这里插入图片描述

  • C语言代码实现:

    • 代码展示:

    • /*
       *文件名:CircleList03.c
       *作者  :张水图(解释权归作者所有)
       *日期  : 2023/3/14
       *功能  :讲解约瑟夫问题
       */
      //头文件导入
      #include<stdio.h>
      #include<math.h>
      #include<string.h>
      #include<stdlib.h>
      #include<stdbool.h>
      //计数循环次数,在后续约瑟夫问题中会用到,初始化了头结点,默认为1。
      static int cur =1;
      int startnumber;
      //创建学生节点
      typedef struct student{
          int id;
          struct student* next;
      }stu;
      //创建链表头结点
      stu* head;
      //函数声明
      static void LinkListadd(int strtnumber);
      static void getall();
      static void josepfu(int strtno,int endno);
      static void del(int id);
      static stu* init();
      int main(void) {
          printf("请输入当前班级学生人数:");
          scanf("%d",&startnumber);
          LinkListadd(startnumber);
          //约瑟夫问题解决
          josepfu(1,3);
          system("pause");
          return 0;
      }
      //定义相关函数
      //1、链表初始化
      static void LinkListadd(int strtnumber){
          if(strtnumber<0||strtnumber>=100){
              printf("您输入的班级学生人数有误。");
              return;
          }
          //首先对链表初始化
          head=(stu*) malloc(sizeof (stu));
          head->id=0;
          head->next=NULL;
          if(head==NULL){
              printf("当前堆空间申请失败");
              return;
          }
          for (int i = 1; i <=strtnumber; ++i) {
              if(i==1){
                  head->id=1;
              } else{
                  //创建一个节点指向头结点的链表下一个位置
                  stu* varible=init();
                  stu* temp=head;
                  for (int j = 0; j <cur -1; j++) {
                      temp=temp->next;
                  }
                  //构成环形链表
                  varible->id=i;
                  temp->next=varible;
                  varible->next=head;
                  cur++;
              }
          }
      }
      //2、获取链表所有节点
      static void getall(){
          if(head==NULL){
              printf("当前链表节点为空");
          }
          //遍历所有节点
          stu* temp=head;
          for (int i = 0; i < cur; ++i) {
              printf("id:%d\n",temp->id);
              temp=temp->next;
          }
      }
      //3、约瑟夫问题解决:约瑟夫处理方法:从第n个数着走,第k个取出
      static void josepfu(int strtno,int endno){
          if(head==NULL||strtno<0||strtno>startnumber){
              printf("输入参数有误。");
              return;
          }
          stu* temp=head;
          while (temp->id!=strtno){
              temp=temp->next;
          }
          for (int i = cur; i >0; i--) {
              for (int j = 1; j < endno; ++j) {
                  temp=temp->next;
              }
              del(temp->id);
              temp=temp->next;
          }
      }
      //4、删除指定节点。
      static void del(int id){
          if(head==NULL){
              printf("当前链表节点为空,输入参数有误");
              return;
          }
          stu* temp=head;
          for (int i = cur; i >0; i--) {
              if(temp->next->id==id){
                  if(temp->next->id==1){
                      printf("删除节点id:%d\n",temp->next->id);
                      temp->next=temp->next->next;
                      head=temp->next;
                  } else{
                      printf("删除节点id:%d\n",temp->next->id);
                      temp->next=temp->next->next;
                  }
                  break;
              }
              temp=temp->next;
          }
      }
      //创建多个节点的初始化
      static stu* init(){
          stu* stu1;
          stu1=(stu*) malloc(sizeof (stu1));
          if(stu1==NULL){
              printf("当前节点对空间申请失败,请重新分配堆空间节点");
              return NULL;
          }
          stu1->id=0;
          stu1->next=NULL;
          return stu1;
      }
      
      
    • 结果展示:在这里插入图片描述

6、环形双链表* :

  • 思路大致一样,只需要将尾节点和头结点相连即可。

  • package 算法.数组.链表;
    
    import org.apache.commons.io.input.Tailer;
    
    //环形双链表
    public class CircleListDemo04 {
        public static void main(String[] args) {
            List4 list4 = new List4();
            Node4 node  = new Node4(new Data4(1,"雨花"));
            Node4 node1 = new Node4(new Data4(2,"落叶"));
            Node4 node2 = new Node4(new Data4(3,"流水"));
            Node4 node3 = new Node4(new Data4(4,"山林"));
            Node4 node4 = new Node4(new Data4(5,"大海"));
            Node4 node5 = new Node4(new Data4(6,"雷电"));
            list4.add(node);
            list4.add(node1);
            list4.add(node2);
            list4.add(node3);
            list4.add(node4);
    
    
            list4.getall();
            System.out.println("============================");
            System.out.println("头结点:"+list4.head.toString());
            System.out.println("尾结点:"+list4.tail.toString());
    
    
            System.out.println("============================");
            list4.insert(5,node5);
            System.out.println("插入指定位置后的值遍历的:");
            list4.getall();
            System.out.println("头结点:"+list4.head.toString());
            System.out.println("尾结点:"+list4.tail.toString());
    
    
    
            System.out.println("============================");
            list4.del(6);
            System.out.println("删除指定位置后的值遍历的:");
            list4.getall();
            System.out.println("头结点:"+list4.head.toString());
            System.out.println("尾结点:"+list4.tail.toString());
    
    
        }
    }
    //数据域
    class Data4{
        public  int id;
        public String name;
    
        public Data4(int id, String name) {
            this.id = id;
            this.name = name;
        }
        public  Data4(){
    
        }
    
        @Override
        public String toString() {
            return "Data4{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    //节点域
    class Node4{
        public Node4 pre;
        public Data4 data4;
        public Node4 next;
    
        public Node4(Data4 data4) {
            this.data4 = data4;
        }
        public Node4(){
    
        }
    
        @Override
        public String toString() {
            return data4.toString();
        }
    }
    //链表
    class List4{
        //头结点
        public static Node4 head=new Node4(new Data4(0,""));
        //尾节点
        public Node4 tail=new Node4(new Data4());
        //j
        public int count=0;
    
        //添加元素【】
        public void add(Node4 node4){
            Node4 temp=head;
            for (int i = 0; i < count; i++) {
                temp=temp.next;
            }
            tail=node4;
            temp.next=tail;
            tail.pre=temp;
            tail.next=head;
            head.pre=tail;
            count++;
        }
        //遍历元素
        public void getall(){
            Node4 temp=head;
            while (true){
                temp=temp.next;
                if (temp==head){
                    break;
                }
                System.out.println(temp.toString());
            }
        }
    
        public void insert(int id,Node4 node4){
            Node4 temp=head;
            if (count==0){
                System.out.println("链表为空");
            }
            for (int i = 0; i < count; i++) {
                temp=temp.next;
                if (temp.data4.id==id){
                    if (tail.data4.id==id){
                        tail=node4;
                        temp.next=tail;
                        tail.pre=temp;
                        tail.next=head;
                        head.pre=tail;
                        count++;
                    }else{
                        Node4 cur=temp.next;
                        temp.next=node4;
                        node4.pre=temp;
                        node4.next=cur;
                        cur.pre=node4;
                        count++;
                    }
                    break;
                }
            }
        }
    //默认头结点不能删
        public void del(int id){
            Node4 temp=head;
            if (count==0){
                System.out.println("链表为空");
            }
            for (int i = 0; i < count; i++) {
                temp=temp.next;
                if (temp.data4.id==id){
                    if (tail.data4.id==id){
                        tail.pre.next=tail.next;
                        tail.next.pre=tail.pre;
                        tail=tail.pre;
                        count--;
                    }else {
                        temp.pre.next=temp.next;
                        temp.next.pre=temp.pre;
                        count--;
                    }
                    break;
                }
            }
        }
    }
    
  • 实现结果展示:在这里插入图片描述

  • C语言参照上述代码自行实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值