Java數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)與面試題匯總
1 單鏈表
1.1 單鏈表介紹
由于順序表的插入刪除操作需要移動(dòng)大量的元素,影響了運(yùn)行效率,因此引入了線性表的鏈?zhǔn)酱鎯?chǔ)——單鏈表。單鏈表通過(guò)一組任意的存儲(chǔ)單元來(lái)存儲(chǔ)線性表中的數(shù)據(jù)元素,不需要使用地址連續(xù)的存儲(chǔ)單元,因此它 不要求在邏輯上相鄰的兩個(gè)元素在物理位置上也相鄰。
物理結(jié)構(gòu)示意圖:

邏輯結(jié)構(gòu)示意圖:

關(guān)于單鏈表的一些說(shuō)明:
- 鏈表是以節(jié)點(diǎn)的方式存儲(chǔ)的,每個(gè)節(jié)點(diǎn)包含data和next域,分別表示存儲(chǔ)的數(shù)據(jù)和指向下一個(gè)節(jié)點(diǎn);
- 鏈表的各個(gè)節(jié)點(diǎn)不一定是連續(xù)存儲(chǔ)的;
- 可以根據(jù)實(shí)際需求來(lái)構(gòu)造是否帶有頭節(jié)點(diǎn)的鏈表。
1.2 單鏈表的實(shí)現(xiàn)思路分析
1.2.1 單鏈表的創(chuàng)建與遍歷
單鏈表的創(chuàng)建:
先創(chuàng)建一個(gè) head 頭節(jié)點(diǎn),表示單鏈表的頭;
每添加一個(gè)節(jié)點(diǎn)就直接加入鏈表的最后;
遍歷的思路:
創(chuàng)建一個(gè)輔助指針,用于幫助遍歷整個(gè)鏈表;
當(dāng)指針指向的節(jié)點(diǎn)的next域?yàn)閚ull,說(shuō)明當(dāng)前節(jié)點(diǎn)為最后一個(gè),遍歷完成。 1.2.2 單鏈表節(jié)點(diǎn)的插入與修改
示意圖如下:

- 首先需要通過(guò)遍歷找到需要添加節(jié)點(diǎn)的位置,圖中示意的為a1的位置;
- 新的節(jié)點(diǎn)的next指向a1.next;
- 將該位置,即a1.next指向新的節(jié)點(diǎn)。
修改操作相當(dāng)于上述過(guò)程的簡(jiǎn)化,只需要找到對(duì)應(yīng)的節(jié)點(diǎn)直接修改節(jié)點(diǎn)對(duì)應(yīng)的屬性即可,這里不再贅述。
1.2.3 單鏈表節(jié)點(diǎn)的刪除
刪除序號(hào)為 “2” 的節(jié)點(diǎn)示意圖如下:

思路如下:
- 找到待刪除節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn),示例中則找到序號(hào)為1的節(jié)點(diǎn);
- 讓該節(jié)點(diǎn)的 temp.next = temp.next.next,即可;
- 由于被刪除的節(jié)點(diǎn)沒(méi)有其他的指向,則會(huì)由Java的垃圾回收機(jī)制進(jìn)行回收,無(wú)需處理。
1.3 實(shí)現(xiàn)代碼
StudentNode.java 節(jié)點(diǎn)類:
/**
* @author 興趣使然黃小黃
* @version 1.0
* 鏈表的節(jié)點(diǎn)類,包含學(xué)生信息和next
*/
public class StudentNode {
public String no; //學(xué)號(hào)
public String name; //姓名
public int age; //年齡
public StudentNode next; //指向下一個(gè)節(jié)點(diǎn)
//構(gòu)造器
public StudentNode(String no, String name, int age ){
this.no = no;
this.name = name;
this.age = age;
}
//為了顯示方便
@Override
public String toString() {
return "StudentNode{" +
"no='" + no + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
StudentLinkedList.java 鏈表的實(shí)現(xiàn)類:
/**
* @author 興趣使然黃小黃
* @version 1.0
* 鏈表的實(shí)現(xiàn)類,用于管理眾多StudentNode節(jié)點(diǎn)
*/
public class StudentLinkedList {
//初始化頭節(jié)點(diǎn)
private StudentNode head = new StudentNode("", "", 0);
//獲取頭節(jié)點(diǎn)
public StudentNode getHead() {
return head;
}
//添加節(jié)點(diǎn)
//1.找到當(dāng)前鏈表的最后節(jié)點(diǎn)
//2.將最后節(jié)點(diǎn)的next指向新的節(jié)點(diǎn)
public void add(StudentNode studentNode) {
StudentNode temp = head;
//遍歷鏈表找到最后的節(jié)點(diǎn)
while (temp.next != null) {
//沒(méi)有找到,就后移
temp = temp.next;
}
//最后的節(jié)點(diǎn)的next指向新節(jié)點(diǎn)
temp.next = studentNode;
}
//遍歷 顯示鏈表
public void showList(){
//判斷鏈表是否為空
if (head.next == null){
System.out.println("當(dāng)前鏈表為空");
return;
}
//遍歷 使用輔助指針
StudentNode temp = head;
while (temp != null){
//更新指針
temp = temp.next;
if (temp.next == null){
System.out.print(temp);
break;
}
System.out.print(temp + "--->");
}
}
//插入節(jié)點(diǎn)
//根據(jù)學(xué)號(hào)順序查找添加的位置, 如果存在, 則提示錯(cuò)誤信息
public void addByOrder(StudentNode studentNode){
//尋找的temp應(yīng)該為添加位置的前一個(gè)節(jié)點(diǎn)
StudentNode temp = head;
boolean flag = false; //標(biāo)識(shí)新添加的no是否已經(jīng)存在
while (true){
if (temp.next == null){
//已經(jīng)在鏈表的尾部
break;
}
if (Integer.parseInt(temp.next.no) > Integer.parseInt(studentNode.no)){
//位置找到 插入到temp后
break;
}else if (Integer.parseInt(temp.next.no) == Integer.parseInt(studentNode.no)){
//已經(jīng)存在
flag = true;
break;
}
//移動(dòng)指針
temp = temp.next;
}
if (flag){
System.out.println("\n準(zhǔn)備插入的學(xué)生信息: " + studentNode.no + ",該學(xué)號(hào)已經(jīng)存在,不可添加!");
}else {
studentNode.next = temp.next;
temp.next = studentNode;
}
}
//根據(jù)no學(xué)號(hào)修改學(xué)生信息
public void update(StudentNode studentNode){
if (head.next == null){
System.out.println("當(dāng)前鏈表為空, 無(wú)法修改");
return;
}
StudentNode temp = head.next;
boolean flag = false; //表示是否找到節(jié)點(diǎn)
while (true){
if (temp == null){
break;
}
if (temp.no == studentNode.no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.name = studentNode.name;
temp.age = studentNode.age;
}else {
System.out.println("沒(méi)有找到");
}
}
//刪除節(jié)點(diǎn)
public void delete(String no){
StudentNode temp = head;
boolean flag = false; //標(biāo)志是否找到
//查找到待刪除節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)進(jìn)行刪除操作
while (true){
if (temp.next == null){
//到達(dá)尾部
break;
}
if (temp.next.no == no){
//找到了
flag = true;
break;
}
//遍歷
temp = temp.next;
}
//刪除操作
if (flag){
temp.next = temp.next.next;
System.out.println("刪除成功!");
}else {
System.out.println("要?jiǎng)h除的節(jié)點(diǎn)不存在!");
}
}
}
測(cè)試類:
/**
* @author 興趣使然黃小黃
* @version 1.0
* 測(cè)試鏈表
*/
public class StudentListTest {
public static void main(String[] args) {
StudentNode node1 = new StudentNode("1", "黃小黃", 21);
StudentNode node2 = new StudentNode("2", "懶羊羊", 21);
StudentNode node3 = new StudentNode("3", "沸羊羊", 22);
//創(chuàng)建單鏈表 錄入數(shù)據(jù) 輸出
StudentLinkedList list = new StudentLinkedList();
list.add(node1);
list.add(node2);
list.add(node3);
System.out.println("遍歷鏈表:");
list.showList();
//測(cè)試插入數(shù)據(jù)方法
StudentNode node5 = new StudentNode("5", "美羊羊", 19);
StudentNode node4 = new StudentNode("4", "暖羊羊", 19);
list.addByOrder(node5);
list.addByOrder(node4);
System.out.println("\n依次插入學(xué)號(hào)為5、4的學(xué)生后:");
list.showList();
//測(cè)試修改方法
System.out.println("\n測(cè)試修改方法:");
list.update(new StudentNode("1", "禰豆子", 10));
list.showList();
//測(cè)試刪除方法
System.out.println("\n測(cè)試刪除方法:");
list.delete("1");
list.delete("5");
list.showList();
}
}
實(shí)現(xiàn)結(jié)果:
遍歷鏈表:
StudentNode{no='1', name='黃小黃', age=21}--->StudentNode{no='2', name='懶羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}
依次插入學(xué)號(hào)為5、4的學(xué)生后:
StudentNode{no='1', name='黃小黃', age=21}--->StudentNode{no='2', name='懶羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}--->StudentNode{no='5', name='美羊羊', age=19}
測(cè)試修改方法:
StudentNode{no='1', name='禰豆子', age=10}--->StudentNode{no='2', name='懶羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}--->StudentNode{no='5', name='美羊羊', age=19}
測(cè)試刪除方法:
刪除成功!
刪除成功!
StudentNode{no='2', name='懶羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}
Process finished with exit code 0
2 單鏈表的面試題
2.1 統(tǒng)計(jì)單鏈表中有效節(jié)點(diǎn)數(shù)量
/**
*
* @param head 頭節(jié)點(diǎn)
* @return 返回有效節(jié)點(diǎn)個(gè)數(shù)
*/
public static int getLength(StudentNode head){
if (head.next == null){
return 0;
}
int length = 0;
StudentNode temp = head.next;
while (temp != null){
length++;
temp = temp.next;
}
return length;
}
2.2 新浪–倒數(shù)第k個(gè)節(jié)點(diǎn)
查找鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)
思路分析:
- 編寫一個(gè)方法,接收head頭節(jié)點(diǎn)和index,index表示k;
- 鏈表從頭到尾遍歷,求出長(zhǎng)度(鏈表節(jié)點(diǎn)個(gè)數(shù))size;
- 從第一個(gè)節(jié)點(diǎn),遍歷size-length次,即可找到倒數(shù)第k個(gè)節(jié)點(diǎn)。
參考代碼:
/**
* 獲取單鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)
* @param head 鏈表的頭節(jié)點(diǎn)
* @param index 倒數(shù)第 k 個(gè)元素
* @return 返回倒數(shù)第 k 個(gè)元素,或者 null
*/
public static StudentNode findLastIndexNode(StudentNode head, int index){
//如果鏈表為空
if (head.next == null){
return null;
}
//得到鏈表的長(zhǎng)度(節(jié)點(diǎn)個(gè)數(shù))
int size = getLength(head);
//遍歷 size-index次 得到倒數(shù)第index個(gè)節(jié)點(diǎn)
//數(shù)據(jù)校驗(yàn)
if (index <= 0 || index > size){
return null;
}
//遍歷
StudentNode current = head.next;
for (int i = 0; i < size - index; i++) {
current = current.next;
}
return current;
}
2.3 騰訊–單鏈表的反轉(zhuǎn)
反轉(zhuǎn)單鏈表
思路分析:
- 可以使用頭插法;
- 以原鏈表為模板,每遍歷一個(gè)節(jié)點(diǎn),取出,并接在新鏈表的最前端;
- 原h(huán)ead頭節(jié)點(diǎn),指向新的節(jié)點(diǎn);
- 直到遍歷完為止。
參考代碼:
/**
* 頭插法反轉(zhuǎn)鏈表
* @param head 接收待反轉(zhuǎn)的鏈表
* @return 返回一個(gè)反轉(zhuǎn)后的新鏈表
*/
public static StudentLinkedList reverseList(StudentNode head){
if (head.next == null){
return null;
}
StudentNode old = head.next; //用于遍歷舊鏈表
//創(chuàng)建新鏈表,新鏈表根據(jù)原鏈表遍歷得到
StudentLinkedList newList = new StudentLinkedList();
StudentNode newHead = newList.getHead(); //新鏈表的頭節(jié)點(diǎn)
//遍歷構(gòu)造
boolean flag = true; //標(biāo)記是否為第一次添加
while (old != null){
//頭插法加入到新鏈表中
StudentNode newNode = new StudentNode(old.no, old.name, old.age);
if(flag){
newHead.next = newNode;
newNode.next = null;
flag = false;
}else {
newNode.next = newHead.next;
newHead.next = newNode;
}
old = old.next;
}
return newList;
}
以上方式雖然可以實(shí)現(xiàn)鏈表的反轉(zhuǎn),但是是以返回一個(gè)新的反轉(zhuǎn)鏈表的形式,并沒(méi)有真正意義上實(shí)現(xiàn)原地反轉(zhuǎn),下面介紹另一種方式:
雙指針:
/**
* 雙指針就地反轉(zhuǎn)鏈表
* @param head 接收鏈表的頭節(jié)點(diǎn),方法中會(huì)將鏈表反轉(zhuǎn)
*/
public static void reverse(StudentNode head){
//如果當(dāng)前鏈表為空 或者只有一個(gè)節(jié)點(diǎn) 直接返回即可
if (head.next == null || head.next.next == null){
return;
}
//輔助指針遍歷原來(lái)的鏈表
StudentNode cur = head.next; //當(dāng)前節(jié)點(diǎn)
StudentNode next = null; //指向cur的下一個(gè)節(jié)點(diǎn)
StudentNode reverseHead = new StudentNode("", "", 0);
//遍歷原來(lái)的鏈表,每遍歷一個(gè)節(jié)點(diǎn),就取出,放在新鏈表的最前端
while (cur != null){
next = cur.next; //暫時(shí)保存當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
cur.next = reverseHead.next; //講cur下一個(gè)節(jié)點(diǎn)放在鏈表最前端
reverseHead.next = cur;
cur = next; //cur后移動(dòng)
}
head.next = reverseHead.next;
return;
}
2.4 百度–逆序打印單鏈表
從尾到頭打印單鏈表
方式一: 先將單鏈表反轉(zhuǎn),然后再打印。但是這樣會(huì)破壞掉原有單鏈表的結(jié)構(gòu),而題目要求僅僅是打印,因此不建議!
方式二: 利用棧模擬
將單鏈表的各個(gè)節(jié)點(diǎn)壓入棧中,利用棧先進(jìn)后出的特點(diǎn),實(shí)現(xiàn)逆序打印。
參考代碼:
/**
* 利用棧模擬 實(shí)現(xiàn)鏈表的逆序打印
* @param head 鏈表的頭節(jié)點(diǎn)
*/
public static void reversePrintList(StudentNode head){
if (head.next == null){
return; //空鏈表無(wú)法打印
}
//創(chuàng)建棧模擬逆序打印
Stack<StudentNode> stack = new Stack<>(); //棧
StudentNode cur = head.next;
//將鏈表的所有節(jié)點(diǎn)壓入棧
while (cur != null){
stack.push(cur);
cur = cur.next;
}
//逆序打印
while (!stack.empty()){
//出棧
System.out.println(stack.pop());
}
return;
}
以上就是Java數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)與面試題匯總的詳細(xì)內(nèi)容,更多關(guān)于Java單鏈表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決@FeignClient注入service失敗問(wèn)題
這篇文章主要介紹了解決@FeignClient注入service失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
如何使用Resttemplate和Ribbon調(diào)用Eureka實(shí)現(xiàn)負(fù)載均衡
這篇文章主要介紹了如何使用Resttemplate和Ribbon調(diào)用Eureka實(shí)現(xiàn)負(fù)載均衡,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Feign?日期格式轉(zhuǎn)換錯(cuò)誤的問(wèn)題
這篇文章主要介紹了Feign?日期格式轉(zhuǎn)換錯(cuò)誤的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringMVC靜態(tài)資源訪問(wèn)問(wèn)題如何解決
這篇文章主要介紹了SpringMVC靜態(tài)資源訪問(wèn)問(wèn)題如何解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
在springboot中添加mvc功能的正確姿勢(shì)講解
這篇文章主要介紹了在springboot中添加mvc功能的正確姿勢(shì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡(jiǎn)單的一對(duì)一聊天
這篇文章主要介紹了Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡(jiǎn)單的一對(duì)一聊天,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)Java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04

