单向环形链表
目录:
1、简介
环形单链表和普通单链表几乎一样,唯一不同的就是普通单链表的最后一个节点的next为空,而我们的环形单链表的最后一个节点的net为头节点,形成一个闭环。
如上图,第一个节点也就是头结点,第五个节点就是最后一个节点,但是在环形单链表中next会指向第一个节点,而普通单链表中最后一个节点的next是为空。
2、简单使用
1):创建节点对象
1 /*节点*/ 2 class Node { 3 private int no; 4 private Node next; 5 6 public Node(int no) { 7 this.no = no; 8 } 9 10 public int getNo() { 11 return no; 12 } 13 14 public void setNo(int no) { 15 this.no = no; 16 } 17 18 public Node getNext() { 19 return next; 20 } 21 22 public void setNext(Node next) { 23 this.next = next; 24 } 25 26 @Override 27 public String toString() { 28 return "Node{" + 29 "no=" + no + 30 '}'; 31 } 32 }
2):创建链表对象
1 /*单向环形链表*/ 2 class CircleSingleLinkedList { 3 private Node first = null; 4 }
3):是否为空的方法
1 /*是否为空的方法*/ 2 public boolean isEmpty() { 3 return first == null; 4 }
如果first为空的话,那就肯定是空了。
4):增加一个节点到尾部的方法
1 /*增加一个节点到尾部的方法*/ 2 public boolean addNode(Node node) { 3 /*如果头为空,那么就将头赋值,并且将头的next指向自己,达到环形的效果*/ 4 if (isEmpty()) { 5 first = node; 6 first.setNext(first); 7 return true; 8 } 9 10 /*辅助节点*/ 11 Node tmp = first; 12 while (true) { 13 /*如果当前节点的next为头节点,那么说明它就是尾部节点了。*/ 14 if (tmp.getNext() == first) { 15 /*那么当前节点的next就等于我们新添加的节点*/ 16 tmp.setNext(node); 17 /*既然新添加的节点是在最后面,那么新添加的节点的next就是头节点*/ 18 node.setNext(first); 19 return true; 20 } 21 /**/ 22 tmp = tmp.getNext(); 23 } 24 }
如果当前链表为空,那么直接把node赋值给我们链表的头节点。
否则就遍历,然后遍历到最后一个条件的条件是当前节点的下一个节点等于头节点,那么就说明当前节点就是最后一个节点了。
然后找到最后一个节点后,最后一个节点的next等于我们当前加入的节点,需要注意的是当前加入的节点的next要为头节点。
注意:这里遍历到尾部的条件和加入节点后尾部节点的next处理和普通单链表不一致。因为要形成闭环,那么尾部节点的next一定要为first,否则就闭不了。
5):添加一个节点并保持从小到大的顺序
1 /*增加一个节点并保持从小到大顺序方法*/ 2 public boolean addNodeByOrder(Node node) { 3 /*如果头为空,那么就将头赋值,并且将头的next指向自己,达到环形的效果*/ 4 if (isEmpty()) { 5 first = node; 6 first.setNext(first); 7 return true; 8 } 9 /*判断头结点是否比我们需要添加的节点大,如果是那么就得置换first*/ 10 if (first.getNo() > node.getNo()) { 11 node.setNext(first); 12 Node tem = first; 13 first = node; 14 /* 15 设置完成后,因为原最后一个节点指向的并不是我们新的头,而是旧头,所以我们需要在进行更改 16 */ 17 Node tmp2 = tem; 18 while (true) { 19 if (tmp2.getNext() == tem) { 20 tmp2.setNext(node); 21 break; 22 } 23 tmp2 = tmp2.getNext(); 24 } 25 return true; 26 } 27 /*辅助节点*/ 28 Node tmp = first; 29 while (true) { 30 /*当前节点的next大于node,那么就把node放置到当前节点的next中*/ 31 if (tmp.getNext().getNo() > node.getNo()) { 32 node.setNext(tmp.getNext()); 33 tmp.setNext(node); 34 return true; 35 } 36 /*判断是否到达尾部*/ 37 else if (tmp.getNext() == first) { 38 tmp.setNext(node); 39 node.setNext(first); 40 return true; 41 } 42 tmp = tmp.getNext(); 43 } 44 }
这个要稍微麻烦一点,
1、判断头是否为空,如果为空,那么就将加入的这个节点赋值给头,然后停止
2、然后判断一种特殊情况,因为我们的环形单链表中头节点是存储数据的,那么判断头节点是否比我们新添加的node大,如果是那么需要互换位置
注意:如果这种特殊情况成立,那我们置换了first和node的位置以后,链表的尾部其实还是指向的原first,我们需要遍历找到链表的尾部,找到最后一个节点,然后最后一个节点的next等于我们新添加的node。
3、如果以上情况都不成立,那么就遍历单链表,例如当前遍历到的节点为current,然后我们需要添加的节点为node
那么如果current的next的no大于node的no,那么我们我们就需要让current的next为node,然后current的原next就为node的next。
这里不明白的,可以去看看单链表的添加,那个有图,这种情况添加和普通单链表没有区别,所以图我就不再画了,【单链表传送门】
6):增加指定个数的节点
1 /*增加指定数量个节点*/ 2 public boolean addNodeByCount(int count) { 3 /*准备添加的节点的no*/ 4 int startNo = -1; 5 /*如果链表为空,那么默认添加一个头节点,那么既然这里添加了一个头节点,那么startNo要改变,我们后面创建节点的次数也要改变*/ 6 if (first == null) { 7 first = new Node(1); 8 first.setNext(first); 9 count--; 10 startNo = 2; 11 } else { 12 /*否则就找到尾部节点的no然后+1*/ 13 Node temp = first; 14 while (true) { 15 if (temp.getNext() == first) { 16 startNo = temp.getNo() + 1; 17 break; 18 } else { 19 temp = temp.getNext(); 20 } 21 } 22 } 23 /*再循环加入*/ 24 for (int i = 0; i < count; i++) { 25 Node node = new Node(startNo); 26 addNodeByOrder(node); 27 startNo++; 28 } 29 return true; 30 }
1、首先我们添加节点的时候,要知道我们肯定要保持顺序对吧,那么我们增加指定个数的节点那我们添加的节点肯定要比原有链表中的链表的no要大
2、我们定义一个变量startNo来存储最后一个节点的no+1,为什么要+1呢,因为我们后面会直接用这个no进行创建节点。
3、如果链表为空,那么我们直接创建一个节点赋值给first,然后不要忘记设置first的next为自己,然后因为我们first这里这里创建了一次,所以count要--,然后startNo要=2(因为first的no是1嘛)
4、如果链表不为空,那么我们就需要找到最后一个节点的no,然后赋值给startNo
5、然后循环的调用addNodeByCount方法添加节点。
3、约瑟夫问题
原题:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
可以稍微换个说法,数量为n个小孩围成一圈,从第k个小孩开始报数,数到m的那个人出列,然后让产生一个所有小孩出列的顺序。
4、约瑟夫问题是用环形单链表解决
我们假设总共有五个娃,从第二个人开始数,每次数二次,然后最后剩一个人,我们来看看大概是个啥样。
那么
n = 5
k = 2
m = 2
目录:
这就是最开始的样子,first代表链表的头部,end代表链表的尾部
移动到k的位置我们只需要将first和end同时移动k-1次,因为当前first就是在1撒,如果k为2,那么就是移动2-1次。
注意,first本身也算一下哦,然后我们移动m下,m为2,那么就代表着数2下,然后移动1次,也就是m-1。
移动一次以后,我们就找到了第一个需要出列的娃,也就是将first指向的那个一个节点出列,那么既然它出列了,那么first就要指向出列的那个节点的next了。
出列后就成了这个样子,那么第一个出列的就是3。
注意:原先end节点的next是指向的3,我们需要重新的给end节点的end赋值为first
按照上面的法子,继续数数就成了这个样子:
然后把first指向的节点出列
然后第二次出列的就是5。
还是进行数数:
然后再把first指向的节点出列。
那么这次出列的就是2
还是先数数:
然后让first指向的节点出列,就变成了这样
然后就出列完成,总共的顺序就是3 5 2 1 4。
5、本随笔中所有源码
1 package t4; 2 3 import t2.Test; 4 5 /** 6 * @author 自在仙 7 * @create 2020年04月19 21:44 8 */ 9 public class CircleSingleLinkedListDemo { 10 11 public static void main(String[] args) { 12 /* Node node1 = new Node(1); 13 Node node2 = new Node(2); 14 Node node3 = new Node(3); 15 CircleSingleLinkedList list = new CircleSingleLinkedList(); 16 list.addNodeByOrder(node3); 17 list.addNodeByOrder(node2); 18 list.addNodeByOrder(new Node(100)); 19 list.addNodeByCount(5);*/ 20 // list.addNodeByOrder(node1); 21 // list.addNodeByOrder(new Node(0)); 22 // list.display(); 23 // list.addNodeByCount(1); 24 25 CircleSingleLinkedList list = new CircleSingleLinkedList(); 26 list.addNodeByCount(5); 27 list.joseph(2,2,2); 28 29 30 } 31 32 33 } 34 35 /*单向环形链表*/ 36 class CircleSingleLinkedList { 37 private Node first = null; 38 39 /*是否为空的方法*/ 40 public boolean isEmpty() { 41 return first == null; 42 } 43 44 /*增加一个节点到尾部的方法*/ 45 public boolean addNode(Node node) { 46 /*如果头为空,那么就将头赋值,并且将头的next指向自己,达到环形的效果*/ 47 if (isEmpty()) { 48 first = node; 49 first.setNext(first); 50 return true; 51 } 52 53 /*辅助节点*/ 54 Node tmp = first; 55 while (true) { 56 /*如果当前节点的next为头节点,那么说明它就是尾部节点了。*/ 57 if (tmp.getNext() == first) { 58 /*那么当前节点的next就等于我们新添加的节点*/ 59 tmp.setNext(node); 60 /*既然新添加的节点是在最后面,那么新添加的节点的next就是头节点*/ 61 node.setNext(first); 62 return true; 63 } 64 /**/ 65 tmp = tmp.getNext(); 66 } 67 } 68 69 /*增加一个节点并保持从小到大顺序方法*/ 70 public boolean addNodeByOrder(Node node) { 71 /*如果头为空,那么就将头赋值,并且将头的next指向自己,达到环形的效果*/ 72 if (isEmpty()) { 73 first = node; 74 first.setNext(first); 75 return true; 76 } 77 /*判断头结点是否比我们需要添加的节点大,如果是那么就得置换first*/ 78 if (first.getNo() > node.getNo()) { 79 node.setNext(first); 80 Node tem = first; 81 first = node; 82 /* 83 设置完成后,因为原最后一个节点指向的并不是我们新的头,而是旧头,所以我们需要在进行更改 84 */ 85 Node tmp2 = tem; 86 while (true) { 87 if (tmp2.getNext() == tem) { 88 tmp2.setNext(node); 89 break; 90 } 91 tmp2 = tmp2.getNext(); 92 } 93 return true; 94 } 95 /*辅助节点*/ 96 Node tmp = first; 97 while (true) { 98 /*当前节点的next大于node,那么就把node放置到当前节点的next中*/ 99 if (tmp.getNext().getNo() > node.getNo()) { 100 node.setNext(tmp.getNext()); 101 tmp.setNext(node); 102 return true; 103 } 104 /*判断是否到达尾部*/ 105 else if (tmp.getNext() == first) { 106 tmp.setNext(node); 107 node.setNext(first); 108 return true; 109 } 110 tmp = tmp.getNext(); 111 } 112 } 113 114 /*增加指定数量个节点*/ 115 public boolean addNodeByCount(int count) { 116 /*准备添加的节点的no*/ 117 int startNo = -1; 118 /*如果链表为空,那么默认添加一个头节点,那么既然这里添加了一个头节点,那么startNo要改变,我们后面创建节点的次数也要改变*/ 119 if (first == null) { 120 first = new Node(1); 121 first.setNext(first); 122 count--; 123 startNo = 2; 124 } else { 125 /*否则就找到尾部节点的no然后+1*/ 126 Node temp = first; 127 while (true) { 128 if (temp.getNext() == first) { 129 startNo = temp.getNo() + 1; 130 break; 131 } else { 132 temp = temp.getNext(); 133 } 134 } 135 } 136 /*再循环加入*/ 137 for (int i = 0; i < count; i++) { 138 Node node = new Node(startNo); 139 addNodeByOrder(node); 140 startNo++; 141 } 142 return true; 143 } 144 145 /** 146 * @param n 总人数 147 * @param k 第k个人开始 148 * @param m 每次数多少个 149 */ 150 public void joseph(int n, int k, int m) { 151 /* 152 * 1、先判断是否合法 153 * 2、找到尾部节点 154 * 3、从第k个人开始撒,刚开始first是1,那么我们要后移k-1次,end和first都要移动 155 * 4、死循环,循环内进行移动位置,每移动一次,就移出去一次,只要first和end一样的时候,我们就停止循环。 156 * */ 157 if (n < 2 || k > n) { 158 /*不合法*/ 159 throw new RuntimeException("数据不合法,n must > 2,k must < n"); 160 } 161 /*找到尾部节点*/ 162 Node end = first; 163 while (true) { 164 if (end.getNext() == first) { 165 break; 166 } 167 end = end.getNext(); 168 } 169 170 /*开始移动到第k个人*/ 171 for (int i = 1; i < k; i++) { 172 first = first.getNext(); 173 end = end.getNext(); 174 } 175 /*开始移动*/ 176 while (true) { 177 if (first == end) { 178 System.out.println(first.getNo()); 179 break; 180 } 181 /*移动m-1次*/ 182 for (int i = 1; i < m; i++) { 183 /*移动*/ 184 first = first.getNext(); 185 /*end也需要移动*/ 186 end = end.getNext(); 187 } 188 System.out.println(first.getNo()); 189 /*移动完成以后,删除first,end一栋一次*/ 190 /*first*/ 191 first = first.getNext(); 192 /*给end设置next为first*/ 193 end.setNext(first); 194 } 195 196 } 197 198 /*展示链表中的数据*/ 199 public void display() { 200 if (isEmpty()) { 201 throw new RuntimeException("链表为空"); 202 } 203 Node tmp = first; 204 while (true) { 205 System.out.println(tmp); 206 if (tmp.getNext() == first) { 207 break; 208 } 209 tmp = tmp.getNext(); 210 } 211 } 212 213 } 214 215 /*节点*/ 216 class Node { 217 private int no; 218 private Node next; 219 220 public Node(int no) { 221 this.no = no; 222 } 223 224 public int getNo() { 225 return no; 226 } 227 228 public void setNo(int no) { 229 this.no = no; 230 } 231 232 public Node getNext() { 233 return next; 234 } 235 236 public void setNext(Node next) { 237 this.next = next; 238 } 239 240 @Override 241 public String toString() { 242 return "Node{" + 243 "no=" + no + 244 '}'; 245 } 246 }
ps:学艺不精,表达不明,还望海涵。