學習筆記-雙向連結串列與環形連結串列
雙向連結串列
在單鏈表的基礎上,每個節點增加了一個pre屬性指向前一個節點。
因為單鏈表只有next指標,因此只能單向遍歷。而雙向連結串列可以從前或從後遍歷。另外也可以自我刪除,也就是不用藉助被刪節點的前一個節點進行刪除,直接靠自己就行。
增刪改查
節點增加了pre指向前一個節點
//雙向連結串列的節點
class StudentNode2{
public int no;
public String name;
StudentNode2 next;
StudentNode2 pre;
public StudentNode2 (int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
先遍歷找到連結串列中的最後一個元素,再分別連上next和pre
//向連結串列中增加元素,直接加在末尾
public void add(StudentNode2 node){
//遍歷連結串列,找到連結串列的最後
StudentNode2 temp=head;
while (true){
if (temp.next==null){
break;
}else if(temp.next.no==node.no){
throw new RuntimeException("重複新增!" );
}
//遍歷連結串列
temp=temp.next;
}
//temp指向連結串列最後一個元素,向temp後新增新元素
temp.next=node;
node.pre=temp;
}
找到新增位置的前一個,然後node.next=temp.next;連上後續不丟失,temp.next=node;與前面連線,node.pre = temp;反向連線,如果是加在連結串列的最末尾,後面是null,node.next有個空指標異常的問題,因此要加個判斷。
//向連結串列中有順序的新增元素,如果編號重複,新增失敗
public void addByOrder(StudentNode2 node){
//根據編號找到需要新增的位置
StudentNode2 temp=head;
while (true){
if(temp.next==null){
break;
}
if(temp.next.no>node.no){
break;
}
if(temp.next.no==node.no){
throw new RuntimeException("重複新增!");
}
temp=temp.next;
}
node.next=temp.next;
temp.next=node;
node.pre = temp;
if(node.next!=null) {
node.next.pre = node;
}
}
沒啥變化
//根據編號修改連結串列
public void updata(int no,String name){
//遍歷找到需要修改的節點
StudentNode2 temp=head.next;
//是否找到該節點
boolean flag=false;
while (true){
if(temp==null){
break;
}else if(temp.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.name=name;
}else {
System.out.println("沒有這個編號,修改失敗");
}
}
可以自我刪除,因此temp直接指向要被刪除的節點,考慮刪的是最後一個節點,同樣有空指標異常的問題,要加一層判斷。
//根據編號刪除節點
public void del(int no){
//找到這個節點
StudentNode2 temp=head.next;
//是否找到該節點
boolean flag=false;
while (true){
if(temp==null){
break;
}else if(temp.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.pre.next=temp.next;
if(temp.next!=null) {
temp.next.pre = temp.pre;
}
}else {
System.out.println("沒有這個編號,刪除失敗");
}
}
沒什麼變化
//顯示連結串列
public void show(){
//遍歷整個連結串列,並打出資訊
StudentNode2 temp=head.next;
while (true){
if(temp==null){
break;
}
System.out.println(temp);
temp=temp.next;
}
}
環形連結串列
無頭結點的環形連結串列。最後一個節點next指向第一個節點,形成一個閉環。如果只有一個節點,就自己指向自己。
Josephu問題
Josephu 問題為:設編號為 1,2,… n 的 n 個人圍坐一圈,約定編號為 k(1<=k<=n)的人從 1 開始報數,數到m 的那個人出列,它的下一位又從 1 開始報數,數到 m 的那個人又出列,依次類推,直到所有人出列為止,由此
產生一個出隊編號的序列。
用一個不帶頭結點的迴圈連結串列來處理 Josephu 問題:先構成一個有 n 個結點的單迴圈連結串列,然後由 k 結點起從 1 開始計數,計到 m 時,對應結點從連結串列中刪除,然後再從被刪除結點的下一個結點又從 1 開始計數,直到最後一個結點從連結串列中刪除演算法結束。
程式碼實現
節點設定
//小孩節點
class Boy{
public int no;
public Boy next;
public Boy(int no){
this.no=no;
}
}
建立一個有num個節點的迴圈連結串列。
cur-輔助指標-指向要插入的前一個節點
建立時,第一個節點是不一樣的,賦值,然後讓他指向自己形成閉環,然後讓cur指向first,附上初值。
接下來的每一個都是連上前後兩條線,然後讓cur移動到新的節點上。
//按照num建立環形連結串列
public void add(int num){
if(num<1){
System.out.println("資料不對");
return;
}
Boy cur=null;
for(int i=1;i<=num;i++){
Boy boy=new Boy(i);
if(i==1){
first=boy;
first.next=first;
cur=first;
}else {
cur.next = boy;
boy.next = first;
cur = boy;
}
}
}
列印時first不變,藉助輔助變數,直接列印,當cur.next==first時,cur指向最後一個元素,結束迴圈。
public void show(){
Boy cur=first;
if(first==null){
System.out.println("空");
return;
}
while (true){
System.out.println("編號:"+cur.no);
if(cur.next==first){
break;
}
cur=cur.next;
}
}
解決約瑟夫問題,k表示從第幾個人開始,m是間隔幾人,num是總人數,用來建立連結串列。
輔助指標cur永遠指向first前一個節點,也就是一開始指向最後一個節點。
首先把cur移動到末尾
然後first和cur同時移動k-1次,因為從他開始,要讓first指向那個開始的人。first一開始指向1,如果從3開始,只要移動2次。
然後開始出列,當cur=first時代表連結串列裡只剩下一個元素,迴圈停止。
出列時,cur和first同時移動m-1次,實現間隔,然後將first向前一位,cur.next=first刪去目標。
public void out(int k,int m,int num){
if(k<1 | m>num | num<1 |k>num |m<1 |first==null){
System.out.println("誤");
return;
}
Boy cur=first;
//cur去末尾
while (true){
if(cur.next==first){
break;
}
cur=cur.next;
}
//同時移動到k-1處
for (int i=0;i<k-1;i++){
first=first.next;
cur=cur.next;
}
//出列
while (true){
if(cur==first){
System.out.println("剩下:"+cur.no);
break;
}
for (int j=0;j<m-1;j++){
first=first.next;
cur=cur.next;
}
System.out.println("離開:"+first.no);
first=first.next;
cur.next=first;
}
}