数据结构之单链表(合并两个单链表有序化、单链表反转、头插尾插,CRUD)

单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

  看图说话:

  上图是单链表在内存中的存储结构,也许我们常常熟悉的单链表是这样的:

   但是不能单纯的以为它是按顺序存储,这里只是为了形象的展示单链表罢了,它的next域指向的不一定是按某种顺序的。

接下来通过一个小Demo来全面实践单链表

  先来定义结点的属性:假设以《水浒传》中的一位英雄作为一个结点,no代表他的编号,name表示名字,nickname表示昵称,next为堆中指向下一个英雄结点的地址。(请忽略属性权限)

 1 class HeroNode {
 2     public int no;
 3     public String name;
 4     public String nickname;
 5     public HeroNode next;
 6 
 7     public HeroNode(int hNo, String hName, String hNickname) {
 8         this.no = hNo;
 9         this.name = hName;
10         this.nickname = hNickname;
11     }
12 
13     @Override
14     public String toString() {
15         return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
16     }
17 }

  有了结点类,就可以创建头结点,进而实现一条单链表了

private HeroNode head = new HeroNode(0, "", "");

   首先可以以head结点为头结点,来加入结点,添加的方法分为 :1、 按照编号no顺序添加(即升序,但不能存在相同no) 2、尾插法   3、头插法

1、 按照编号no顺序添加(即升序,但不能存在相同no)

  具体操作由两行代码搞定(也是头插法的代码)

 heroNode.next = temp.next;
temp.next = heroNode;

   接着添加第二个结点(no小于第一个结点)


 

 具体代码如下:

 1 public void addByOrder(HeroNode heroNode) {//传入一个结点
 2         HeroNode temp = head;//用一个temp变量代替head,方便遍历
 3         boolean flag = false;//标记单链表中是否存在与heroNode节点no相同的结点
 4         while (true) {//遍历单链表进行判断
 5             if (temp.next == null)//如果头节点指向为空,则直接跳出循环
 6                 break;
 7             if (temp.next.no > heroNode.no)//传入的结点no与单链表中的no比较
 8                 break;
 9             if (temp.next.no == heroNode.no) {//有重复,则标记true
10                 flag = true;
11                 break;
12             }
13             temp = temp.next;//遍历,指向下一个结点
14         }
15         if (!flag) {//前两个if跳出循环,都会执行,具体看下图
16             heroNode.next = temp.next;
17             temp.next = heroNode;
18         } else {
19             System.out.println("存在" + heroNode.no);
20         }
21     }

2、尾插法

 1 public void addEnd(HeroNode heroNode) {
 2         // 尾插法需要遍历单链表到最后一个结点的下一个结点为null的上一个结点
 3         HeroNode temp = head;
 4         while (true) {
 5             if (temp.next == null)
 6                 break;
 7             else
 8                 temp = temp.next;
 9         }
10         temp.next = heroNode;
11     }

3、头插法

1 public void addFirst(HeroNode heroNode) {
2         HeroNode temp = head;
3         heroNode.next = temp.next;
4         temp.next = heroNode;
5     }

进行了单链表的添加后,就要进行简单的更新(修改和删除),这里就比较简单了,注意这里的头结点不可删除

由于第二个结点在堆内存中没有指向任何结点,而且没有任何节点指向改no为2的结点,于是该对象随后会被GC回收掉

删除结点代码:

 1 public void delete(HeroNode heroNode) {
 2         HeroNode temp =head;
 3         while (true) {
 4             if (temp.next == null) {
 5                 System.out.println("链表为空");
 6                 break;
 7             }
 8             if (temp.next == heroNode) {
 9                 temp.next = heroNode.next;
10                 heroNode.next = null;
11                 break;
12             }
13             temp = temp.next;
14         }
15     }

修改结点代码:

 1 public void update(HeroNode heroNode, String name) {
 2         HeroNode temp = head;
 3         while (true) {
 4             if (temp.next == null) {
 5                 System.out.println("链表为空");
 6                 break;
 7             }
 8             if (temp.next == heroNode) {
 9                 heroNode.name = name;
10                 break;
11             }
12             temp = temp.next;
13         }
14     }

  ok,到此单链表的增删改查结束了,接下来进行进阶:单链表反转、合并两个单链表并有序

单链表反转(其实就是头插法,不难)

 借助一个新的头结点来生成反向单链表,最后再由原来的头结点指向这个新的头结点的下一个结点即可,分析图如下:

 temp用来代替头结点进行移动操作,next用于保存temp的下一个结点,reverseNode为辅助头结点

   得到:

  进行头插法

 

 

 具体反转单链表代码:

 1 public void revList() {
 2         // 关键就是借助了下面这个辅助头节点
 3         HeroNode reverseNode = new HeroNode(0, "", "");
 4         // 下面这个是要保存当前节点的下一个节点的
 5         HeroNode next = null;
 6         HeroNode temp = head.next;// 将head后的一大串单链表copy一份交给temp管理
 7         while (true) {
 8             if (temp == null) {
 9                 break;// 单链表为空
10             }
11             next = temp.next;// 保存下一个节点,比如当前temp->1,那么next就等于temp->2
12             temp.next = reverseNode.next;// 1的next赋值为null,下一次就是2的next赋值为1
13             reverseNode.next = temp;// 这一步就是上面“下一次里的next”赋值为1的操作
14             temp = next;// 到这一步的时候,本次temp已经和原来的单链表断开了,所以需要next提前保存temp.next
15         }
16         head.next = reverseNode.next;
17     }

合并两个单链表并有序

  思路:将按照升序加入结点的两个单链表进行升序合并。

 以此规律可得到结果:

 具体升序合并单链表代码:

 1 public SingleLinkedList merge(SingleLinkedList sl, SingleLinkedList sl2) {
 2         SingleLinkedList ss = new SingleLinkedList();
 3         HeroNode temp1 = sl.head.next;
 4         HeroNode temp2 = sl2.head.next;
 5         HeroNode next = null;
 6         while (true) {
 7             if (temp1 == null && temp2 == null) {//当两个链表都遍历完时
 8                 break;
 9             }
10             if (temp1 == null && temp2 != null) {//当其中一个链表还有结点,但是另一个已经遍历完,则将此链表剩余部分依次加入新链表ss
11                 next=temp2.next;
12                 ss.addByOrder(temp2);
13                 temp2 = next;
14             } else if (temp2 == null && temp1 != null) {//同上
15                 next=temp1.next;
16                 ss.addByOrder(temp1);
17                 temp1 = next;
18             }
19             if (temp1 != null && temp2 != null) {//都没有遍历完
20                 if (temp1.no < temp2.no) {//把no小的加入新链表
21                     next = temp1.next;//保存下一节点
22                     ss.addByOrder(temp1);//这一步会导致temp1指向改变,导致链表丢失
23                     temp1 = next;//所以需要恢复到temp1得下一个
24                 } else {
25                     next = temp2.next;
26                     ss.addByOrder(temp2);
27                     temp2 = next;
28                 }
29             }
30         }
31         return ss;
32     }

所有随笔,皆为复习巩固。如有错误,欢迎指正。

原文地址:https://www.cnblogs.com/taichiman/p/12829185.html