数据结构Java实现04----循环链表、仿真链表

简介:
  • 单向循环链表
  • 双向循环链表
  • 仿真链表

 

一、单向循环链表:

1、概念:

单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个

和单链表相比,循环单链表的长处是从链尾到链头比较方便。当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表。

和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为方便。

601e4b9e-aed1-4c1a-ad3b-fd722c5d39de

带头结点的循环单链表的操作实现方法和带头结点的单链表的操作实现方法类同,差别仅在于:

(1)在构造函数中,要加一条head.next = head 语句,把初始时的带头结点的循环单链表设计成上图中(a)所示的状态。

(2)在index(i)成员函数中,把循环结束判断条件current != null改为current != head。

 

2、单链表的代码实现:

先回顾上一篇文章,定位到第三段“三、单项链表的代码实现”,我们是需要修改这段里面的(3)LinkList.java代码,(1)和(2)的代码不变。

(3)LinkList.java:单项循环链表类:(核心代码)

复制代码
 1 //单向循环链表类
 2 public class CycleLinkList implements List {
 3 
 4     Node head; //头指针
 5     Node current;//当前结点对象
 6     int size;//结点个数
 7     
 8     //初始化一个空链表
 9     public CycleLinkList()
10     {
11         //初始化头结点,让头指针指向头结点。并且让当前结点对象等于头结点。
12         this.head = current = new Node(null);
13         this.size =0;//单向链表,初始长度为零。
14         this.head.next = this.head;
15     }
16     
17     //定位函数,实现当前操作对象的前一个结点,也就是让当前结点对象定位到要操作结点的前一个结点。
18     //比如我们要在a2这个节点之前进行插入操作,那就先要把当前节点对象定位到a1这个节点,然后修改a1节点的指针域
19     public void index(int index) throws Exception
20     {
21         if(index <-1 || index > size -1)
22         {
23           throw new Exception("参数错误!");    
24         }
25         //说明在头结点之后操作。
26         if(index==-1)    //因为第一个数据元素结点的下标是0,那么头结点的下标自然就是-1了。
27             return;
28         current = head.next;
29         int j=0;//循环变量
30         while(current != head&&j<index)
31         {
32             current = current.next;
33             j++;
34         }
35         
36     }    
37     
38     @Override
39     public void delete(int index) throws Exception {
40         // TODO Auto-generated method stub
41         //判断链表是否为空
42         if(isEmpty())
43         {
44             throw new Exception("链表为空,无法删除!");
45         }
46         if(index <0 ||index >size)
47         {
48             throw new Exception("参数错误!");
49         }
50         index(index-1);//定位到要操作结点的前一个结点对象。
51         current.setNext(current.next.next);
52         size--;
53     }
54 
55     @Override
56     public Object get(int index) throws Exception {
57         // TODO Auto-generated method stub
58         if(index <-1 || index >size-1)
59         {
60             throw new Exception("参数非法!");
61         }
62         index(index);
63         
64         return current.getElement();
65     }
66 
67     @Override
68     public void insert(int index, Object obj) throws Exception {
69         // TODO Auto-generated method stub
70         if(index <0 ||index >size)
71         {
72             throw new Exception("参数错误!");
73         }
74         index(index-1);//定位到要操作结点的前一个结点对象。
75         current.setNext(new Node(obj,current.next));
76         size++;
77     }
78 
79     @Override
80     public boolean isEmpty() {
81         // TODO Auto-generated method stub
82         return size==0;
83     }
84     @Override
85     public int size() {
86         // TODO Auto-generated method stub
87         return this.size;
88     }
89     
90     
91 }
复制代码

 

14行是添加的代码,30行是修改的代码。

 

3、单项循环链表的应用举例:

编写击鼓传花小游戏。

游戏规则:N个人围成一个圈,从第一个人开始传花,当数到M时,该人退出游戏,直到剩下最后一个人。

代码实现:

(4)Game.java:

复制代码
 1 //游戏类
 2 public class Game {
 3 
 4     //单向循环链表
 5     CycleLinkList list = new CycleLinkList();
 6     //总人数
 7     int num;
 8     //数到几退出
 9     int key;
10     
11     //游戏初始化方法
12     public Game(int num,int key)
13     {
14        this.num = num;
15        this.key = key;
16     }
17     
18     public void play() throws Exception
19     {
20        for(int i=0;i<num;i++)
21        {
22            list.insert(i, i);  
23        }
24        
25        System.out.println("\n-------游戏开始之前---------\n");
26        for(int i=0;i<list.size;i++)
27        {
28            System.out.print(list.get(i)+" ");
29        }
30        System.out.println("\n-------游戏开始---------\n");
31        int iCount=num; //开始等于总人数num
32        int j=0; //累加器,计算是否能被key整除。
33        
34        Node node = list.head;
35        while(iCount!=1)
36        {
37           if(node.getElement()!=null&& Integer.parseInt(node.getElement().toString())!=-1)
38           {
39             j++;  
40             if(j%key==0)
41             {
42                 node.setElement(-1);
43                 iCount--;
44                 System.out.println();
45                 for(int i=0;i<list.size;i++)
46                 {
47                    System.out.print(list.get(i)+" ");
48                 }
49             }
50           } 
51           node = node.next;
52        }
53        System.out.println("\n-------游戏结束---------\n");
54        for(int i=0;i<list.size;i++)
55        {
56            System.out.print(list.get(i)+" ");
57        }
58     }
59     
60 }
复制代码

 

(5)Test.java:

复制代码
 1 public class Test {
 2 
 3     /**
 4      * @param args
 5      */
 6     public static void main(String[] args) throws Exception {
 7         // TODO Auto-generated method stub
 8       /*
 9       CycleLinkList list = new CycleLinkList();
10       for(int i=0;i<10;i++)
11       {
12          int temp = ((int)(Math.random()*100))%100;
13          list.insert(i, temp);
14          System.out.print(temp+" ");
15       }
16       list.delete(4);
17       System.out.println("\n------删除第五个元素之后-------");
18       for(int i=0;i<list.size;i++)
19       {
20           System.out.print(list.get(i)+" ");
21       }*/
22         
23       Game game = new Game(10,3);
24       game.play();
25     
26     }
27 }
复制代码

 

 

二、双向循环链表:

双向链表:

  双向链表是每个结点除后继指针外还有一个前驱指针。和单链表类同,双向链表也有带头结点结构和不带头结点结构两种,带头结点的双向链表更为常用;另外,双向链表也可以有循环和非循环两种结构,循环结构的双向链表更为常用。

双向循环链表:

  在双向链表中,每个结点包括三个域,分别是element域、next域和prior域,其中element域为数据元素域,next域为指向后继结点的对象引用,prior域为指向前驱结点的对象引用。下图为双向链表结点的图示结构:

4f137f3c-cff0-4c7d-beb2-aa37da010812

如下图是带头结点的双向循环链表的图示结构。双向循环链表的next和prior各自构成自己的单向循环链表:

3bf0b73d-1b95-4445-bbf4-5d0d6ca00f37

在双向链表中,有如下关系:设对象引用p表示双向链表中的第i个结点,则p.next表示第i+1个结点,p.next.prior仍表示第i个结点,即p.next.prior == p;同样地,p.prior表示第i-1个结点,p.prior.next仍表示第i个结点,即p.prior.next == p。下图是双向链表上述关系的图示:

3d014245-d195-4829-88fd-a66ddde03783

双向循环链表的插入过程:

  下图中的指针p表示要插入结点的位置,s表示要插入的结点,①、②、③、④表示实现插入过程的步骤:

ff952782-ed50-4c3b-92cc-4eea4fbe63e9

循环双向链表的删除过程:

  下图中的指针p表示要插入结点的位置,①、②表示实现删除过程的步骤:

be7a99c0-7ed3-4fbd-837a-6d229710a683

2、双向循环链表的代码实现:

(1)List.java:

复制代码
 1 //线性表接口
 2 public interface List {
 3     //获得线性表长度
 4     public int size();
 5 
 6     //判断线性表是否为空
 7     public boolean isEmpty();
 8 
 9     //插入元素
10     public void insert(int index, Object obj) throws Exception;
11 
12     //删除元素
13     public void delete(int index) throws Exception;
14 
15     //获取指定位置的元素
16     public Object get(int index) throws Exception;
17 }
复制代码

 

(2)Node.java:

复制代码
 1 //结点类
 2 public class Node {
 3 
 4     Object element; //数据域
 5     Node next;  //后继指针域
 6     Node prior; //前驱指针域
 7 
 8     //头结点的构造方法
 9     public Node(Node nextval) {
10         this.next = nextval;
11     }
12 
13     //非头结点的构造方法
14     public Node(Object obj, Node nextval) {
15         this.element = obj;
16         this.next = nextval;
17     }
18 
19     //获得当前结点的后继结点
20     public Node getNext() {
21         return this.next;
22     }
23 
24     //获得当前结点的前驱结点
25     public Node getPrior() {
26         return this.prior;
27     }
28 
29     //获得当前的数据域的值
30     public Object getElement() {
31         return this.element;
32     }
33 
34     //设置当前结点的后继指针域
35     public void setNext(Node nextval) {
36         this.next = nextval;
37     }
38 
39     //设置当前结点的前驱指针域
40     public void setPrior(Node priorval) {
41         this.prior = priorval;
42     }
43 
44     //设置当前结点的数据域
45     public void setElement(Object obj) {
46         this.element = obj;
47     }
48 
49     public String toString() {
50         return this.element.toString();
51     }
52 }
复制代码

 

(3)DoubleCycleLinkList.java:

复制代码
 1 //单向链表类
 2 public class DoubleCycleLinkList implements List {
 3 
 4     Node head; //头指针
 5     Node current;//当前结点对象
 6     int size;//结点个数
 7 
 8     //初始化一个空链表
 9     public DoubleCycleLinkList() {
10         //初始化头结点,让头指针指向头结点。并且让当前结点对象等于头结点。
11         this.head = current = new Node(null);
12         this.size = 0;//单向链表,初始长度为零。
13         this.head.next = head;
14         this.head.prior = head;
15     }
16 
17     //定位函数,实现当前操作对象的前一个结点,也就是让当前结点对象定位到要操作结点的前一个结点。
18     public void index(int index) throws Exception {
19         if (index < -1 || index > size - 1) {
20             throw new Exception("参数错误!");
21         }
22         //说明在头结点之后操作。
23         if (index == -1)
24             return;
25         current = head.next;
26         int j = 0;//循环变量
27         while (current != head && j < index) {
28             current = current.next;
29             j++;
30         }
31 
32     }
33 
34     @Override
35     public void delete(int index) throws Exception {
36         // TODO Auto-generated method stub
37         //判断链表是否为空
38         if (isEmpty()) {
39             throw new Exception("链表为空,无法删除!");
40         }
41         if (index < 0 || index > size) {
42             throw new Exception("参数错误!");
43         }
44         index(index - 1);//定位到要操作结点的前一个结点对象。
45         current.setNext(current.next.next);
46         current.next.setPrior(current);
47         size--;
48     }
49 
50     @Override
51     public Object get(int index) throws Exception {
52         // TODO Auto-generated method stub
53         if (index < -1 || index > size - 1) {
54             throw new Exception("参数非法!");
55         }
56         index(index);
57 
58         return current.getElement();
59     }
60 
61     @Override
62     public void insert(int index, Object obj) throws Exception {
63         // TODO Auto-generated method stub
64         if (index < 0 || index > size) {
65             throw new Exception("参数错误!");
66         }
67         index(index - 1);//定位到要操作结点的前一个结点对象。
68         current.setNext(new Node(obj, current.next));
69         current.next.setPrior(current);
70         current.next.next.setPrior(current.next);
71 
72         size++;
73     }
74 
75     @Override
76     public boolean isEmpty() {
77         // TODO Auto-generated method stub
78         return size == 0;
79     }
80 
81     @Override
82     public int size() {
83         // TODO Auto-generated method stub
84         return this.size;
85     }
86 
87 
88 }
复制代码

 

(4)Test.java:

复制代码
 1 public class Test {
 2 
 3     /**
 4      * @param args
 5      */
 6     public static void main(String[] args) throws Exception {
 7         // TODO Auto-generated method stub
 8         DoubleCycleLinkList list = new DoubleCycleLinkList();
 9         for (int i = 0; i < 10; i++) {
10             int temp = ((int) (Math.random() * 100)) % 100;
11             list.insert(i, temp);
12             System.out.print(temp + " ");
13         }
14         list.delete(4);
15         System.out.println("\n------删除第五个元素之后-------");
16         for (int i = 0; i < list.size; i++) {
17             System.out.print(list.get(i) + " ");
18         }
19     }
20 
21 }
复制代码

 

 

运行效果:

5d799e54-9b23-443a-a5ed-6caae0a0ee86

 

三、仿真链表:

在链式存储结构中,我们实现数据元素之间的次序关系依靠指针。我们也可以用数组来构造仿真链表。方法是在数组中增加一个(或两个)int类型的变量域,这些变量用来表示后一个(或前一个)数据元素在数组中的下标。我们把这些int类型变量构造的指针称为仿真指针。这样,就可以用仿真指针构造仿真的单链表(或仿真的双向链表)。

bc2c2b10-4edc-4644-b095-c00ca04b8766

相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
79 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
44 1
|
2月前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
83 2
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
63 2
|
18天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
24天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
存储 算法 Java
Java 中常用的数据结构
【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
30 6
|
2月前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
30 1
|
2月前
|
存储 算法 Java
Java常用的数据结构
【10月更文挑战第3天】 在 Java 中,常用的数据结构包括数组、链表、栈、队列、树、图、哈希表和集合。每种数据结构都有其特点和适用场景,如数组适用于快速访问,链表适合频繁插入和删除,栈用于实现后进先出,队列用于先进先出,树和图用于复杂关系的表示和查找,哈希表提供高效的查找性能,集合用于存储不重复的元素。合理选择和组合使用这些数据结构,可以显著提升程序的性能和效率。
|
2月前
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
31 6
下一篇
无影云桌面