資料結構學習3-單向連結串列、雙向連結串列以及使用連結串列解決約瑟夫環
阿新 • • 發佈:2021-02-14
連結串列學習筆記
連結串列是一種使用十分頻繁的資料結構,它的優點在於可以將大量的資料儲存在分散的空間內,當需要插入或修改節點的時候,只需要修改節點之間的指標即可。
與之相反,陣列的儲存則需要連續的空間,當需要向陣列中插入資料或者修改順序的時候,需要對整個空間進行處理。
所以:我們常說 如果查詢的比較頻繁就使用陣列,如果修改比較頻繁那麼建議使用連結串列
連結串列是以節點的方式來進行資料儲存的,它分為帶頭節點的連結串列和不帶頭節點的連結串列
單向連結串列
每一個節點中都分為data域和next域 ,data域存放資料 ,next域指向下一個節點
程式碼實現
public class SingleLinkedList {
/**
* 頭節點中沒有任何資料 它只是一個輔助節點
*/
private SingleLinkedNode head;
public SingleLinkedList() {
head = new SingleLinkedNode(-1, null);
}
/**
* 新增節點
*
* @param node
*/
public void add(SingleLinkedNode node) {
SingleLinkedNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
/**
* 按排序新增
*
* @param node
*/
public void addByRank(SingleLinkedNode node) {
SingleLinkedNode temp = head;
while (true) {
if (temp.next != null) {
if (temp.next.rank > node.rank) {
node.next = temp.next;
temp.next = node;
break;
} else {
temp = temp.next;
}
} else {
temp.next = node;
break;
}
}
}
/**
* 移除節點
*
* @param rank
*/
public SingleLinkedNode remove(int rank) {
SingleLinkedNode temp = head;
SingleLinkedNode removed = null;
while (true) {
if (temp.next != null) {
if (temp.next.rank == rank) {
removed = temp.next;
temp.next = temp.next.next;
break;
}
// 往後面移動一位
temp = temp.next;
} else {
// 不存在當前移除的key
break;
}
}
return removed;
}
public void print() {
// 列印
SingleLinkedNode lastNode = head.next;
System.out.println("head");
while (lastNode != null) {
System.out.println(" -> " + lastNode);
lastNode = lastNode.next;
}
}
/**
* 從尾部開始列印
* 利用棧 先進後出的原則
*/
public void printFromEnd() {
Stack<String> stack = new Stack<>();
SingleLinkedNode lastNode = head.next;
while (lastNode != null) {
stack.add(" -> " + lastNode);
lastNode = lastNode.next;
}
System.out.println("printFromEnd");
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
/**
* 連結串列的有效長度
*
* @return
*/
public int size() {
int len = 0;
SingleLinkedNode temp = head;
while (temp.next != null) {
len++;
temp = temp.next;
}
return len;
}
/**
* 從尾部開始查詢到第n個節點
*
* @param index 下標都是從0開始計算
* @return
*/
public SingleLinkedNode lastIndexOf(int index) {
// 先獲取到真正的長度
int size = size();
if (index >= size) {
throw new RuntimeException("out of bound:" + index + "[" + size + "]");
}
// 計算正向的下標
int realIndex = size - 1 - index;
SingleLinkedNode temp = head;
int curIndex = -1;
while (temp.next != null) {
curIndex++;
temp = temp.next;
if (realIndex == curIndex) {
return temp;
}
}
return null;
}
/**
* 反轉
* 原連結串列是 head - 1 -> 2 -> 5
* 反轉之後 head - 5 -> 2 -> 1
* <p>
* 思路:
* tempHead -> 1
* tempHead -> 2 -> 1
* tempHead -> 5 -> 2 -> 1
*/
public void reverse() {
if (head.next == null) {
return;
}
// 當前迴圈遍歷的節點
SingleLinkedNode cur = head.next;
// 當前迴圈的節點的下一個節點
SingleLinkedNode next;
// 反轉的臨時頭節點
SingleLinkedNode reverseHead = new SingleLinkedNode(0, "");
while (cur != null) {
// 先獲取到下一個節點
next = cur.next;
// 斷開原本當前節點和下一個節點之間的關係 並構建新的關係
// 這一句很巧妙 實現了將1掛在2的後面
cur.next = reverseHead.next;
// 將當前節點設定為反轉連結串列的第一個節點
reverseHead.next = cur;
// 連結串列向後移動
cur = next;
}
head.next = reverseHead.next;
}
/**
* 合併2個有序的連結串列
* @param another
* @return
*/
public SingleLinkedList merge( SingleLinkedList another){
SingleLinkedNode aTemp = this.head.next;
SingleLinkedNode bTemp = another.head.next;
SingleLinkedList mergeList = new SingleLinkedList();
SingleLinkedNode mergeNode = mergeList.head ;
while(aTemp != null && bTemp != null){
// 比較
if(aTemp.rank > bTemp.rank){
mergeNode.next = aTemp;
aTemp = aTemp.next;
}else{
mergeNode.next = bTemp;
bTemp = bTemp.next;
}
mergeNode = mergeNode.next;
}
if(aTemp == null){
mergeNode.next = bTemp;
}else {
mergeNode.next = aTemp;
}
return mergeList;
}
static class SingleLinkedNode {
private int rank;
private String name;
private SingleLinkedNode next;
public SingleLinkedNode(int rank, String name) {
this.rank = rank;
this.name = name;
}
@Override
public String toString() {
return "SingleLinkedNode{" +
"rank=" + rank +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(new SingleLinkedNode(1, "zhangsan"));
singleLinkedList.add(new SingleLinkedNode(3, "lisi"));
singleLinkedList.add(new SingleLinkedNode(5, "wangwu"));
singleLinkedList.print();
singleLinkedList.remove(4);
singleLinkedList.print();
singleLinkedList.addByRank(new SingleLinkedNode(4, "haha"));
singleLinkedList.print();
singleLinkedList.remove(4);
singleLinkedList.print();
System.out.println("size:" + singleLinkedList.size());
System.out.println("\tlastIndexOf(2):" + singleLinkedList.lastIndexOf(2));
System.out.println("\tlastIndexOf(1):" + singleLinkedList.lastIndexOf(1));
System.out.println("\tlastIndexOf(0):" + singleLinkedList.lastIndexOf(0));
System.out.println("reverse:");
singleLinkedList.reverse();
singleLinkedList.print();
singleLinkedList.printFromEnd();
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
singleLinkedList2.add(new SingleLinkedNode(8, "zhaoliu"));
singleLinkedList2.add(new SingleLinkedNode(3, "songqi"));
singleLinkedList2.add(new SingleLinkedNode(2, "songqi2"));
System.out.println("merge:");
singleLinkedList.merge(singleLinkedList2).print();
}
}
雙向連結串列
和單向連結串列不同的點在於雙向連結串列除了data域和next域以外還有一個pre域,它指向當前節點的上一個節點
所以:雙向連結串列可以自刪除,可以反向查詢
程式碼實現
public class DoubleLinkedList {
private DoubleLinkedNode head;
public DoubleLinkedList() {
head = new DoubleLinkedNode(-1, "頭節點");
}
public void add(DoubleLinkedNode node) {
// 找到最後一個節點
DoubleLinkedNode temp = head;
while (temp.next != null) {
temp = temp.next;
}
// 雙向繫結
temp.next = node;
node.pre = temp;
}
public void addByRank(DoubleLinkedNode node) {
DoubleLinkedNode temp = head;
while (temp.next != null) {
if (temp.next.rank > node.rank) {
break;
}
temp = temp.next;
}
if (temp.next != null) {
temp.next.pre = node;
node.next = temp.next;
}
// 雙向繫結
temp.next = node;
node.pre = temp;
}
/**
* 移除節點
*
* @param rank
*/
public DoubleLinkedNode remove(int rank) {
DoubleLinkedNode temp = head;
DoubleLinkedNode removed = null;
while (true) {
if (temp.next != null) {
if (temp.next.rank == rank) {
removed = temp.next;
DoubleLinkedNode preNode = removed.pre;
DoubleLinkedNode nextNode = removed.next;
preNode.next = nextNode;
if (nextNode != null) {
nextNode.pre = preNode;
}
break;
}
// 往後面移動一位
temp = temp.next;
} else {
// 不存在當前移除的key
break;
}
}
return removed;
}
/**
* 列印資料
*/
public void print() {
System.out.println("head:");
DoubleLinkedNode temp = head;
while (temp.next != null) {
System.out.println("\t -> " + temp.next);
temp = temp.next;
}
System.out.println();
}
/**
* 從尾部開始列印
*/
public void printFromEnd() {
// 首先找到最後一個節點
DoubleLinkedNode temp = head;
// 向後查詢
while (temp.next != null) {
temp = temp.next;
}
System.out.println("printFromEnd");
// 向前查詢
while (temp.pre != null) {
System.out.println("\t -> " + temp);
temp = temp.pre;
}
}
/**
* 連結串列的有效長度
* @return
*/
public int size() {
int len = 0;
DoubleLinkedNode temp = head;
while (temp.next != null) {
len++;
temp = temp.next;
}
return len;
}
/**
* 從尾部開始查詢到第n個節點
*
* @param index 下標都是從0開始計算
* @return
*/
public DoubleLinkedNode lastIndexOf(int index) {
// 先獲取到最後一個節點
DoubleLinkedNode temp = head;
int len = 0;
while (temp.next != null) {
temp = temp.next;
len++;
}
if (index >= len) {
throw new RuntimeException("out of bound:" + index + "[" + len + "]");
}
// 向前找
while (index > 0) {
temp = temp.pre;
index--;
}
return temp;
}
/**
* 反轉
*/
public void reverse() {
if(head.next == null){
return;
}
// 首先需要一個臨時的反轉節點
DoubleLinkedNode reverseHead = new DoubleLinkedNode(-1, "");
// 迴圈原連結串列
DoubleLinkedNode cur = head.next;
while(cur!= null){
// 先獲取到下一個節點
DoubleLinkedNode next = cur.next;
// 反轉pre和當前節點的關係
cur.pre = cur;
// 處理next的關係
cur.next = reverseHead.next;
// 將當前節點綁到新的頭節點上
reverseHead.next = cur;
// 向後移動
cur = next;
}
// 重新繫結頭節點
head.next = reverseHead.next;
reverseHead.next.pre = head;
}
static class DoubleLinkedNode {
private int rank;
private String name;
private DoubleLinkedNode pre;
private DoubleLinkedNode next;
public DoubleLinkedNode(int rank, String name) {
this.rank = rank;
this.name = name;
}
@Override
public String toString() {
return "DoubleLinkedNode{" +
"rank=" + rank +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(new DoubleLinkedNode(1, "zhangsan"));
doubleLinkedList.add(new DoubleLinkedNode(3, "lisi"));
doubleLinkedList.add(new DoubleLinkedNode(5, "wangwu"));
doubleLinkedList.print();
doubleLinkedList.remove(3);
doubleLinkedList.print();
doubleLinkedList.addByRank(new DoubleLinkedNode(4, "zhaoliu"));
doubleLinkedList.print();
doubleLinkedList.printFromEnd();
System.out.println("size:" + doubleLinkedList.size());
System.out.println("\tlastIndexOf(2):" + doubleLinkedList.lastIndexOf(2));
System.out.println("\tlastIndexOf(1):" + doubleLinkedList.lastIndexOf(1));
System.out.println("\tlastIndexOf(0):" + doubleLinkedList.lastIndexOf(0));
System.out.println("reverse:");
doubleLinkedList.reverse();
doubleLinkedList.print();
}
}
使用不帶頭節點的單向環形連結串列實現約瑟夫環
單向迴圈連結串列
如圖所示,不帶頭節點的單線環形連結串列中 最後一個節點的next域會指向第一個節點,所以我們可以通過這個資料結構實現約瑟夫環
約瑟夫環
程式碼實現
public class Josephu {
static class Children {
private Child first;
public Children(int nums) {
addChild(nums);
}
public void addChild(int nums) {
first = new Child(1);
Child temp = first;
for (int i = 2; i <= nums; i++) {
Child child = new Child(i);
temp.next = child;
temp = child;
}
temp.next = first;
}
public void print() {
System.out.println("head:");
Child temp = first;
if (temp != null) {
System.out.println("\t -> " + temp);
while (temp.next != first) {
temp = temp.next;
System.out.println("\t -> " + temp);
}
}
}
public void calculate(int num) {
System.out.println("calculate split:" + num);
int size = size();
int splitSpace = (num - 1) % size;
if(splitSpace == 0){
splitSpace = size;
}
Child cur = first;
Child next;
int curIndex = 0;
while (size > 1) {
curIndex++;
if (curIndex == splitSpace) {
// 需要移除下一個
next = cur.next;
System.out.println("\t -> "+next);
cur.next = next.next;
// 長度-1
size--;
// 重新計算間隔位置
splitSpace = (num - 1) % size;
if(splitSpace == 0){
splitSpace = size;
}
// 給當前節點當作最後一個
curIndex = 0;
}
cur = cur.next;
}
System.out.println("\t -> "+cur);
}
public int size() {
if (first == null) {
return 0;
}
int len = 1;
Child temp = first;
while (temp.next != first) {
temp = temp.next;
len++;
}
return len;
}
}
static class Child {
int no;
Child next;
public Child(int no) {
this.no = no;
}
@Override
public String toString() {
return "Child{" +
"no=" + no +
'}';
}
}
public static void main(String[] args) {
Children children = new Children(5);
children.print();
// 1 2 3 4 5 的結果應該是 2 -> 4 -> 1 -> 5 -> 3
children.calculate(7);
}
}