鏈表(Linked List)
上一篇文章分析了List源碼,這一篇文章本來要分析Set的源碼,發現Set的底層是使用HashMap實現的,於是準備先分析Map,但是發現map的實現類的底層數據結構是數組,鏈表,紅黑樹,撤了撤了,先講一波數據結構,數組大家都比較熟悉,所以我們主要講鏈表和樹結構,這一篇文章先學習鏈表。
鏈表的定義
鏈表是以節點(node)存儲的鏈式存儲結構,一個node包含一個data域(存放數據)和一個next域(存放下一個node的指針),鏈表的各個節點不一定是連續的,它可以分爲帶頭結點和不帶頭結點。頭結點僅包含next域。
下面我們主要講解單向鏈表、雙向鏈表和單向循環鏈表(約瑟夫問題)
1、單向鏈表
單向鏈表的特點是鏈接方向是單向的,對鏈表的訪問要從頭部開始進行順序讀取,鏈表中的每個結點僅有一個指向下一個結點的指針。
1.1構造鏈表
創建節點類,包含了data和next,next指向下一個節點對象
class PersonNode {
public int number;//每個結點的序號,方便我們進行排序和查找
public String name;//結點的名稱
public String nickName;//結點的別名
public PersonNode next;//指向下一結點的指針
public PersonNode(int number, String name, String nickName) {
this.number = number;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "PersonNode{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
創建單向鏈表類,裏面包含了增刪改查的方法
class SingleLinkedList {
//創建頭結點
private PersonNode headNode = new PersonNode(0, "", "");
public PersonNode getHeadNode() {
return headNode;
}
//添加節點的單向鏈表
/**
* 在鏈表尾部插入
*/
public void addNode(PersonNode personNode) {
......
}
/**
* 按照number順序插入
*/
public void addByOrder(PersonNode personNode) {
......
}
/**
* 修改節點信息
*/
public void update(PersonNode personNode) {
......
}
/**
* 刪除某一個節點
* 先根據number查找,在刪除
*/
public void delete(PersonNode personNode) {
......
}
/**
* 遍歷節點
*/
//顯示鏈表中的有效節點
public void show(PersonNode personNode) {
......
}
1.2單向鏈表增刪改查的實現
我們將會以圖解的方式講解,
在鏈表中增加一個節點
確定要插入的節點位置,循環遍歷到該位置節點的上一個節點。
S->next=P->next
P->next=S
代碼實現
/**
* 按照number順序插入
*/
public void addByOrder(PersonNode personNode) {
//定義插入標識符
boolean flag = false;
//定義替代變量
PersonNode temp = headNode;
while (true) {
if (temp.next == null) {
break;
}
//滿足條件
if (temp.next.number > personNode.number) {
break;
} else if (temp.next.number == personNode.number) {
flag = true;
break;
}
//後移
temp = temp.next;
}
//插入節點
if (flag) {
System.out.println("節點已經存在~~~~");
} else {
personNode.next = temp.next;
temp.next = personNode;
}
}
在鏈表中刪除一個節點
確定刪除的位置,循環遍歷到該位置節點的上一個節點
P->next=p->next->next
代碼實現
/**
* 刪除某一個節點
* 先根據number查找,在刪除
*/
public void delete(PersonNode personNode) {
boolean flag = false;
PersonNode temp = headNode;
while (true) {
if (temp.next == null) {
System.out.println("鏈表爲空");
break;
}
if (temp.next.number == personNode.number) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.println("找不到該節點的信息");
}
}
修改鏈表中的一個節點信息
確定要修改節點的位置,先遍歷找到該節點,修改該節點的信息。
/**
* 修改節點信息
*/
public void update(PersonNode personNode) {
boolean flag = false;
PersonNode temp = headNode;
//根據編號找到要修改的節點
while (true) {
if (temp.next == null) {
System.out.println("鏈表爲空");
break;
}
if (temp.next.number == personNode.number) {
flag = true;
break;
}
temp = temp.next;
}
//修改節點信息
if (flag) {
temp.next.nickName = personNode.nickName;
temp.next.name = personNode.name;
} else {
System.out.println("找不到該節點的信息");
}
}
遍歷鏈表
/**
* 遍歷節點
*/
//顯示鏈表中的有效節點
public void show(PersonNode personNode) {
if (personNode.next == null) {
System.out.println("該鏈表爲空~~~");
}
PersonNode temp = personNode;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
System.out.println(temp);
}
}
2、雙向鏈表
雙向鏈表的定義
雙向鏈表中的數據結點有兩個指針,分別指向直接後繼和直接前驅,所以從雙向鏈表中的任一結點開始,都可以很方便的訪問它的前驅結點和後繼結點。
2.1構造雙向鏈表
它和單向鏈表很相似,僅僅是增加了一個直接前驅結點
public int number;
public String name;
public String nickName;
public PersonNode next;
public PersonNode pre;
2.2雙向鏈表增刪改查的實現
雙向鏈表的遍歷
雙向鏈表的遍歷與單向鏈表相同,可以按照兩個方向遍歷,在這裏就不在代碼實現了。
雙向鏈表的添加
1、在末尾添加
在雙向鏈表的末尾添加C節點(定位到節點B)
B->next=C
C->pre=B
代碼實現
/**
* 1、在末尾添加節點
*/
public void add(PersonNode2 personNode2){
PersonNode2 temp=headNode;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
}
//構成雙向鏈表
temp.next=personNode2;
personNode2.pre=temp;
}
2、在兩節點間添加
在節點B和節點C間添加D(程序定位到節點D),
順序:先搞定D的前驅和後繼節點,再搞定C的節點的前驅和B的後繼節點。
D->pre=B
D->next=C
C->pre=D
B->next=D
代碼實現
/**
*在兩節間插入新的結點
*/
public void addByOrder(PersonNode2 personNode2){
PersonNode2 temp=headNode;
boolean flag=false;
boolean flag2=false;
boolean flag3=false;
while (true){
if (temp.next==null){
flag2=true;
break;
}
//加入兩個節點間的條件
if(temp.next.number>personNode2.number){
flag3=true;
break;
}
if(temp.next.number==personNode2.number){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
System.out.println("相同的節點已存在");
}
if(flag2){
//在末尾添加構成雙向鏈表
temp.next=personNode2;
personNode2.pre=temp;
}
if(flag3){
//在兩個有效節點間添加
personNode2.pre= temp;
personNode2.next=temp.next;
temp.next.pre=personNode2;
temp.next=personNode2;
}
}
雙向鏈表的修改
同單鏈表修改相同
雙向鏈表的刪除
在兩節點間刪除(定位到節點S(D))
S->pre->next=S->next
S->next->pre=S->pre
代碼實現
/**
* 刪除節點
*/
public void delete(PersonNode2 personNode2){
PersonNode2 temp=headNode;
boolean flag=false;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
if(temp.number==personNode2.number){
flag=true;
break;
}
}
if(flag){
temp.pre.next=temp.next;
if(temp.next!=null){
temp.next.pre=temp.pre;
}
}else {
System.out.println("找不到刪除節點的信息~~");
}
}
3、單向循環鏈表(無頭結點)
定義
單向循環鏈表是單鏈表的另一種形式,其結構特點是最後一個節點不再是結束標記,而是指向整個鏈表的第一個結點,從而形成一個環。
約瑟夫問題是單向循環鏈表的一個典型的應用,所以下面通過學習約瑟夫問題來學習單向循環鏈表。
約瑟夫問題描述:
約瑟夫問題是一個非常著名的趣題,即由n個小男孩坐成一圈,按順時針由1開始給他們編號。然後由第一個人開始報數,數到m的人出局。現在需要求的是最後一個出局的人的編號。給定兩個參數 n和m,分別代表遊戲的人數和報數大小。請返回最後一個出局的人的編號.
問題分析:
約瑟夫問題可以簡單分爲兩個步驟
構造Boy節點類
private int number;
private Boy next;
構建一個單向循環鏈表
定義兩個指針變量First和cur,first表示頭結點,cur指向當前節點
/**
*構造第一個節點first
*/
first = boy;//第一個節點爲first
first.setNext(first);//僅有一個節點時,first指向first,構成閉環
cur = first;//輔助變量指向first
/**
*構造其它節點
*/
cur.setNext(boy);//將新添加的節點設置爲當前節點的後繼節點
boy.setNext(first);//新添加的節點與first構成閉環
cur=boy;//將當前節點設置爲新添加的節點
遍歷鏈表,根據約定的規則退出循環鏈表,直至僅有一個節點
定義兩個輔助節點變量first和helper,first指向每一輪要開始的節點,helper指向最後一個節點,當first與helper指向同一個節點時,該循環鏈表僅剩一個節點
構造函數
moveCycle(int no,int number,int sum)
1、確定從第幾個節點開始
for(int i=0;i<no-1;i++){
first=first.getNext();
helper=helper.getNext();
}
2、循環,找出節點,直至僅剩一個節點
while (true){
if(first==helper){
System.out.printf("最後一個節點時%d\n",first.getNumber());
break;
}
//開始,每number下,出圈一次
for(int j=0;j<number-1;j++){
first=first.getNext();
helper=helper.getNext();
}
System.out.printf("%d出圈\n",first.getNumber());
first=first.getNext();
helper.setNext(first);
}
往期推薦
Redis高頻面試題及答案
大白話布隆過濾器,又能和面試官扯皮了~天天用Redis,持久化方案有哪些你知道嗎?
面試官:你知道哪幾種事務失效的場景?
天天寫 order by,你知道Mysql底層執行流程嗎?萬字長文帶你入門Zookeeper!!!求你了,別再問我Zookeeper如何實現分佈式鎖了!!!