一、單向循環鏈表
單向循環鏈表是單鏈表的另一種形式,其結構特點是鏈表中最後一個結點的指針不再是結束標記,而是指向整個鏈表的第一個結點,從而使單鏈表形成一個環。和單鏈表相比,循環單鏈表的長處是從鏈尾到鏈頭比較方便。當要處理的數據元素序列具有環型結構特點時,適合於採用循環單鏈表。
和單鏈表相同,循環單鏈表也有帶頭結點結構和不帶頭結點結構兩種,帶頭結點的循環單鏈表實現插入和刪除操作時,算法實現較爲方便。帶頭結點的循環單鏈表結構如下:
帶頭結點的循環單鏈表的操作實現方法和帶頭結點的單鏈表的操作實現方法類同,差別僅在於:(a) 空鏈表 (b)非空鏈表
(1)在構造函數中,要加一條head.next = head 語句,把初始時的帶頭結點的循環單鏈表設計成圖2-11 (a)所示的狀態。
(2)在index(i)成員函數中,把循環結束判斷條件current != null改爲current != head。
實現:
public interface List { //插入元素 public void add(int index,Object obj) throws Exception; //刪除元素 public void delete(int index) throws Exception; //獲取某個元素 public Object get(int index) throws Exception; //判斷是否爲空 public boolean isEmpty(); //獲得集合的長度 public int size(); }
//節點類 public class Node { //數據域 Object element; //指針域 Node next; //頭結點的構造方法 public Node(Node nextval) { this.next=nextval; } //非頭結點的額構造方法 public Node(Object obj,Node nextval) { this.element=obj; this.next=nextval; } //獲取當前數據域的值 public Object getElement() { return this.element; } //獲取當前節點的後繼節點 public Node getNode() { return this.next; } //設置當前數據域的值 public void setElement(Object obj) { this.element=obj; } //設置當前節點的指針域 public void setNode(Node node) { this.next=node; } public String toString() { return this.element.toString(); } }
public class CycleLinkedList implements List { //頭指針 Node head; //當前節點對象 Node current; //節點個數 int size; public CycleLinkedList() { //初始化頭結點,讓頭指針指向頭結點,並且讓當前節點對象等於頭結點 this.head=current=new Node(null); this.size=0; this.head.next=head; } //定爲函數,將當前節點對象定爲到前一個節點 public void index(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("參數錯誤!"); } //說明在頭結點之後操作 if(index==-1) return; current=head.next; //循環變量 int j=0; while(current!=head&&j<index) { current=current.next; j++; } } //增加元素 @Override public void add(int index, Object obj) throws Exception { if(index<0||index>size) { throw new Exception("增加元素參數錯誤!"); } //定爲到操作對象的前一個對象 index(index-1); current.setNode(new Node(obj,current.next)); size++; } //刪除元素 @Override public void delete(int index) throws Exception { if(isEmpty()) { throw new Exception("鏈表爲空,刪除錯誤!"); } if(index<0||index>size) { throw new Exception("刪除元素參數錯誤!"); } index(index-1); current.setNode(current.next.next); size--; } @Override public Object get(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("參數錯誤!"); } index(index); return current.getElement(); } @Override public boolean isEmpty() { return size==0; } @Override public int size() { return size; } }
結果:import java.util.Random; public class Main { public static void main(String[] args) throws Exception { CycleLinkedList list=new CycleLinkedList(); Random random=new Random(); for (int i = 0; i < 10; i++) { int temp=random.nextInt(100); list.add(i, temp); System.out.print(temp+" "); } System.out.println(); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } System.out.println(); list.delete(5); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } } }
二、雙向循環鏈表
雙向鏈表是每個結點除後繼指針外還有一個前驅指針。和單鏈表類同,雙向鏈表也有帶頭結點結構和不帶頭結點結構兩種,
帶頭結點的雙向鏈表更爲常用;另外,雙向鏈表也可以有循環和非循環兩種結構,循環結構的雙向鏈表更爲常用。
在雙向鏈表中,每個結點包括三個域,分別是element域、next域和prior域,其中element域爲數據元素域,next域爲指向
後繼結點的對象引用,prior域爲指向前驅結點的對象引用。下圖爲雙向鏈表結點的圖示結構。
雙向鏈表的圖示結構
如下圖是帶頭結點的循環雙向鏈表的圖示結構。循環雙向鏈表的next和prior各自構成自己的循環單鏈表。
在雙向鏈表中,有如下關係:設對象引用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。下圖是
雙向鏈表上述關係的圖示。
循環雙向鏈表的插入過程如下圖所示。圖中的指針p表示要插入結點的位置,s表示要插入的結點,①、②、③、④表示
實現插入過程的步驟。
循環雙向鏈表的刪除過程如下圖所示。圖中的指針p表示要插入結點的位置,①、②表示實現刪除過程的步驟。
實現:
//節點類
public class Node {
//數據域
Object element;
//後繼指針域
Node next;
//前驅指針域
Node prior;
//頭結點的構造方法
public Node(Node nextval) {
this.next=nextval;
}
//非頭結點的額構造方法
public Node(Object obj,Node nextval) {
this.element=obj;
this.next=nextval;
}
//獲取當前數據域的值
public Object getElement() {
return this.element;
}
//獲取當前節點的後繼節點
public Node getNext() {
return this.next;
}
//獲取當前節點的前驅節點
public Node getPrior() {
return this.prior;
}
//設置當前數據域的值
public void setElement(Object obj) {
this.element=obj;
}
//設置當前後繼節點的指針域
public void setNext(Node node) {
this.next=node;
}
//設置當前前驅節點的指針域
public void setPrior(Node priorval) {
this.prior=priorval;
}
public String toString() {
return this.element.toString();
}
}
public class DoubleCycleLinkedList implements List {
//頭指針
private Node head;
//當前節點對象
private Node current;
//節點個數
int size;
public DoubleCycleLinkedList() {
//初始化頭結點,讓頭指針指向頭結點,並且讓當前節點對象等於頭結點
this.head=current=new Node(null);
this.size=0;
this.head.next=head;
this.head.prior=head;
}
//定爲函數,將當前節點對象定爲到前一個節點
public void index(int index) throws Exception {
if(index<-1||index>size-1) {
throw new Exception("參數錯誤!");
}
//說明在頭結點之後操作
if(index==-1)
return;
current=head.next;
//循環變量
int j=0;
while(current!=head&&j<index) {
current=current.next;
j++;
}
}
//增加元素
@Override
public void add(int index, Object obj) throws Exception {
if(index<0||index>size) {
throw new Exception("增加元素參數錯誤!");
}
//定爲到操作對象的前一個對象
index(index-1);
current.setNext(new Node(obj,current.next));
current.next.setPrior(current);
current.next.next.setPrior(current.next);
size++;
}
//刪除元素
@Override
public void delete(int index) throws Exception {
if(isEmpty()) {
throw new Exception("鏈表爲空,刪除錯誤!");
}
if(index<0||index>size) {
throw new Exception("刪除元素參數錯誤!");
}
index(index-1);
current.setNext(current.next.next);
current.next.setPrior(current);
size--;
}
@Override
public Object get(int index) throws Exception {
if(index<-1||index>size-1) {
throw new Exception("參數錯誤!");
}
index(index);
return current.getElement();
}
@Override
public boolean isEmpty() {
return size==0;
}
@Override
public int size() {
return size;
}
}
public class Main {
public static void main(String[] args) throws Exception {
DoubleCycleLinkedList list=new DoubleCycleLinkedList();
Random random=new Random();
for (int i = 0; i < 10; i++) {
int temp=random.nextInt(100);
list.add(i, temp);
System.out.print(temp+" ");
}
System.out.println();
for(int j=0;j<list.size();j++) {
System.out.print(list.get(j)+" ");
}
System.out.println();
list.delete(5);
for(int j=0;j<list.size();j++) {
System.out.print(list.get(j)+" ");
}
}
}
三、仿真鏈表
在鏈式存儲結構中,我們實現數據元素之間的次序關係依靠指針。我們也可以用數組來構造仿真鏈表。方法是在數組中增加一個(或兩個)int類型的變量域,這些變量用來表示後一個(或前一個)數據元素在數組中的下標。我們把這些int類型變量構造的指針稱爲仿真指針。這樣,就可以用仿真指針構造仿真的單鏈表(或仿真的雙向鏈表)。
(a)常規單鏈表 (b)仿真單鏈表一 (c)仿真單鏈表二
四、循環鏈表應用
編寫擊鼓傳花小遊戲。
遊戲規則:N個人圍成一個圈,從第一個人開始傳花,當數到M時,該人退出遊戲,直到剩下最後一個人。
//擊鼓傳花 思路:將num個數加入到單向循環鏈表中,從第一個數開始數,數到key,將key置爲-1,最後剩下的一個不爲-1的數就是餘下的那個人 public class Game { CycleLinkedList list=new CycleLinkedList(); //總人數 private int num; //數到幾退出 private int key; public Game(int num,int key) { this.num=num; this.key=key; } public void play() throws Exception { for (int i = 0; i < num; i++) { list.add(i, i); } System.out.println("遊戲開始之前............"); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } System.out.println(); System.out.println("遊戲開始之後............"); //開始等於總人數 int iCount=num; //累加器,看能否被key整除 int j=0; Node node=list.head; while(iCount!=1) { if(node.getElement()!=null&&Integer.parseInt(node.getElement().toString())!=-1) { j++; if(j%key==0) { node.setElement(-1); iCount--; System.out.println(); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } } } node=node.next; } System.out.println(); System.out.println("遊戲結束..............."); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } } public static void main(String[] args) throws Exception { Game game=new Game(6,3); game.play(); } }
輸出結果: