蒐集整理鏈表的簡單操作

1、單鏈表的創建和遍歷:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class LinkList {
public Node head;
public Node current;
 
//方法:向鏈表中添加數據
public void add(int data) {
 //判斷鏈表爲空的時候
 if (head == null) {//如果頭結點爲空,說明這個鏈表還沒有創建,那就把新的結點賦給頭結點
 head = new Node(data);
 current = head;
 } else {
 //創建新的結點,放在當前節點的後面(把新的結點合鏈表進行關聯)
 current.next = new Node(data);
 //把鏈表的當前索引向後移動一位
 current = current.next; //此步操作完成之後,current結點指向新添加的那個結點
 }
}
 
//方法:遍歷鏈表(打印輸出鏈表。方法的參數表示從節點node開始進行遍歷
public void print(Node node) {
 if (node == null) {
 return;
 }
 
current = node;
 while (current != null) {
 System.out.println(current.data);
 current = current.next;
 }
}
 
class Node {
//注:此處的兩個成員變量權限不能爲private,因爲private的權限是僅對本類訪問。
 int data; //數據域
 Node next;//指針域
 
 public Node(int data) {
 this.data = data;
}
}
 
 
public static void main(String[] args) {
LinkList list = new LinkList();
//向LinkList中添加數據
 for (int i = 0; i < 10; i++) {
 list.add(i);
 }
 
 list.print(list.head);// 從head節點開始遍歷輸出
}
 
}

上方代碼中,這裏面的Node節點採用的是內部類來表示(33行)。使用內部類的最大好處是可以和外部類進行私有操作的互相訪問。

注:內部類訪問的特點是:內部類可以直接訪問外部類的成員,包括私有;外部類要訪問內部類的成員,必須先創建對象。

爲了方便添加和遍歷的操作,在LinkList類中添加一個成員變量current,用來表示當前節點的索引(03行)。

這裏面的遍歷鏈表的方法(20行)中,參數node表示從node節點開始遍歷,不一定要從head節點遍歷。

 

2、求單鏈表中節點的個數:

注意檢查鏈表是否爲空。時間複雜度爲O(n)。這個比較簡單。

核心代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方法:獲取單鏈表的長度
public int getLength(Node head) {
 if (head == null) {
 return 0;
 }
 
 int length = 0;
 Node current = head;
 while (current != null) {
 length++;
 current = current.next;
 }
 
 return length;
}

3、查找單鏈表中的倒數第k個結點:

3.1  普通思路:

先將整個鏈表從頭到尾遍歷一次,計算出鏈表的長度size,得到鏈表的長度之後,就好辦了,直接輸出第(size-k)個節點就可以了(注意鏈表爲空,k爲0,k爲1,k大於鏈表中節點個數時的情況

)。時間複雜度爲O(n),大概思路如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int findLastNode(int index) { //index代表的是倒數第index的那個結點
 
 //第一次遍歷,得到鏈表的長度size
 if (head == null) {
 return -1;
 }
 
 current = head;
 while (current != null) {
 size++;
 current = current.next;
}
 
 //第二次遍歷,輸出倒數第index個結點的數據
 current = head;
 for (int i = 0; i < size - index; i++) {
 current = current.next;
 }
 
return current.data;
}

如果面試官不允許你遍歷鏈表的長度,該怎麼做呢?接下來就是。

 3.2  改進思路:(這種思路在其他題目中也有應用)

     這裏需要聲明兩個指針:即兩個結點型的變量first和second,首先讓first和second都指向第一個結點,然後讓second結點往後挪k-1個位置,此時first和second就間隔了k-1個位置,然後整體向後移動這兩個節點,直到second節點走到最後一個結點的時候,此時first節點所指向的位置就是倒數第k個節點的位置。時間複雜度爲O(n)

代碼實現:(初版)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Node findLastNode(Node head, int index) {
 
 if (node == null) {
 return null;
 }
 
 Node first = head;
 Node second = head;
 
 //讓second結點往後挪index個位置
 for (int i = 0; i < index; i++) {
 second = second.next;
 }
 
 //讓first和second結點整體向後移動,直到second結點爲Null
while (second != null) {
 first = first.next;
 second = second.next;
 }
 
 //當second結點爲空的時候,此時first指向的結點就是我們要找的結點
return first;
}

代碼實現:(最終版)(考慮k大於鏈表中結點個數時的情況時,拋出異常)

上面的代碼中,看似已經實現了功能,其實還不夠健壯:

  要注意k等於0的情況;

  如果k大於鏈表中節點個數時,就會報空指針異常,所以這裏需要做一下判斷。

核心代碼如下:

   

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public Node findLastNode(Node head, int k) {
if (k == 0 || head == null) {
 return null;
 }
 
 Node first = head;
 Node second = head;
 
//讓second結點往後挪k-1個位置
 for (int i = 0; i < k - 1; i++) {
 System.out.println("i的值是" + i);
 second = second.next;
 if (second == null) { //說明k的值已經大於鏈表的長度了
 //throw new NullPointerException("鏈表的長度小於" + k); //我們自己拋出異常,給用戶以提示
  return null;
 }
}
 
 //讓first和second結點整體向後移動,直到second走到最後一個結點
 while (second.next != null) {
 first = first.next;
 second = second.next;
 }
 //當second結點走到最後一個節點的時候,此時first指向的結點就是我們要找的結點
return first;
}

 

4、查找單鏈表中的中間結點:

同樣,面試官不允許你算出鏈表的長度,該怎麼做呢?

思路:

    和上面的第2節一樣,也是設置兩個指針first和second,只不過這裏是,兩個指針同時向前走,second指針每次走兩步,first指針每次走一步,直到second指針走到最後一個結點時,此時first指針所指的結點就是中間結點。注意鏈表爲空,鏈表結點個數爲1和2的情況。時間複雜度爲O(n)。

代碼實現:

  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//方法:查找鏈表的中間結點
public Node findMidNode(Node head) {
 
if (head == null) {
 return null;
}
 
Node first = head;
 Node second = head;
//每次移動時,讓second結點移動兩位,first結點移動一位
while (second != null && second.next != null) {
 first = first.next;
 second = second.next.next;
}
 
//直到second結點移動到null時,此時first指針指向的位置就是中間結點的位置
 return first;
}

上方代碼中,當n爲偶數時,得到的中間結點是第n/2 + 1個結點。比如鏈表有6個節點時,得到的是第4個節點。

 

5、合併兩個有序的單鏈表,合併之後的鏈表依然有序:

    這道題經常被各公司考察。

例如:

鏈表1:

  1->2->3->4

鏈表2:

  2->3->4->5

合併後:

  1->2->2->3->3->4->4->5

解題思路:

  挨着比較鏈表1和鏈表2。

  這個類似於歸併排序。尤其要注意兩個鏈表都爲空、和其中一個爲空的情況。只需要O (1) 的空間。時間複雜度爲O (max(len1,len2))

代碼實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//兩個參數代表的是兩個鏈表的頭結點
public Node mergeLinkList(Node head1, Node head2) {
 
if (head1 == null && head2 == null) { //如果兩個鏈表都爲空
 return null;
}
 if (head1 == null) {
 return head2;
}
 if (head2 == null) {
 return head1;
}
 
Node head; //新鏈表的頭結點
 Node current; //current結點指向新鏈表
// 一開始,我們讓current結點指向head1和head2中較小的數據,得到head結點
 if (head1.data < head2.data) {
 head = head1;
 current = head1;
 head1 = head1.next;
 } else {
 head = head2;
 current = head2;
 head2 = head2.next;
}
 
 while (head1 != null && head2 != null) {
 if (head1.data < head2.data) {
  current.next = head1; //新鏈表中,current指針的下一個結點對應較小的那個數據
  current = current.next; //current指針下移
  head1 = head1.next;
 } else {
 current.next = head2;
  current = current.next;
  head2 = head2.next;
 }
 }
 
 //合併剩餘的元素
 if (head1 != null) { //說明鏈表2遍歷完了,是空的
 current.next = head1;
 }
 
if (head2 != null) { //說明鏈表1遍歷完了,是空的
 current.next = head2;
}
 
 return head;
}

代碼測試:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
LinkList list1 = new LinkList();
LinkList list2 = new LinkList();
//向LinkList中添加數據
for (int i = 0; i < 4; i++) {
 list1.add(i);
 }
 
for (int i = 3; i < 8; i++) {
 list2.add(i);
}
 
LinkList list3 = new LinkList();
list3.head = list3.mergeLinkList(list1.head, list2.head); //將list1和list2合併,存放到list3中
 
list3.print(list3.head);// 從head節點開始遍歷輸出
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章