博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单链表的创建和遍历、求单链表中节点的个数、查找单链表中的中间结点、判断单链表是否有环、取出有环链表中环的长度,删除有序链表中的重复结点...
阅读量:6859 次
发布时间:2019-06-26

本文共 10029 字,大约阅读时间需要 33 分钟。

hot3.png

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;

}

 

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个节点。

 

判断单链表是否有环:

  这里也是用到两个指针,如果一个链表有环,那么用一个指针去遍历,是永远走不到头的。

  因此,我们用两个指针去遍历:first指针每次走一步,second指针每次走两步,如果first指针和second指针相遇,说明有环。时间复杂度为O (n)。

方法:

  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//方法:判断单链表是否有环

public boolean hasCycle(Node head) {

 

 if (head == null) {

 return false;

 }

 

 Node first = head;

 Node second = head;

 

 while (second != null) {

 first = first.next; //first指针走一步

 second = second.next.next; second指针走两步

 

 if (first == second) { //一旦两个指针相遇,说明链表是有环的

  return true;

 }

}

 

 return false;

}

完整版代码:(包含测试部分)

这里,我们还需要加一个重载的add(Node node)方法,在创建单向循环链表时要用到。

 

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

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

LinkList.java:

 

 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;

  }

 }

  

  

 //方法重载:向链表中添加结点

 public void add(Node node) {

  if (node == null) {

  return;

  }

  

  if (head == null) {

  head = node;

  current = head;

  } else {

  current.next = node;

  current = current.next;

  }

 }

  

  

 //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历

 public void print(Node node) {

  if (node == null) {

  return;

  }

  

  current = node;

  while (current != null) {

  System.out.println(current.data);

  current = current.next;

  }

 }

  

 //方法:检测单链表是否有环

 public boolean hasCycle(Node head) {

  

  if (head == null) {

  return false;

  }

  

  Node first = head;

  Node second = head;

  

  while (second != null) {

  first = first.next; //first指针走一步

  second = second.next.next; //second指针走两步

  

  if (first == second) { //一旦两个指针相遇,说明链表是有环的

   return true;

  }

  }

  

  return false;

 }

  

 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 < 4; i++) {

  list.add(i);

  }

  

  list.add(list.head); //将头结点添加到链表当中,于是,单链表就有环了。备注:此时得到的这个环的结构,是下面的第8小节中图1的那种结构。

  

  System.out.println(list.hasCycle(list.head));

 }

 }

检测单链表是否有环的代码是第50行。

88行:我们将头结点继续往链表中添加,此时单链表就环了。最终运行效果为true。

如果删掉了88行代码,此时单链表没有环,运行效果为false。

 

取出有环链表中,环的长度:

我们平时碰到的有环链表是下面的这种:(图1)

上图中环的长度是4。

但有可能也是下面的这种:(图2)

此时,上图中环的长度就是3了。

那怎么求出环的长度呢?

 

//方法:判断单链表是否有环。返回的结点是相遇的那个结点

public Node hasCycle(Node head) {

 

 if (head == null) {

 return null;

 }

 

 Node first = head;

 Node second = head;

 

while (second != null) {

 first = first.next;

 second = second.next.next;

 

 if (first == second) { //一旦两个指针相遇,说明链表是有环的

  return first; //将相遇的那个结点进行返回

 }

 }

 return null;

}

 

//方法:有环链表中,获取环的长度。参数node代表的是相遇的那个结点

public int getCycleLength(Node node) {

 

 if (head == null) {

 return 0;

 }

 

 Node current = node;

 int length = 0;

 

 while (current != null) {

 current = current.next;

 length++;

 if (current == node) { //当current结点走到原点的时候

  return length;

 }

 }

 return length;

}

 

完整版代码:(包含测试部分)

 

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

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

public class LinkList {

 public Node head;

 public Node current;

  

 public int size;

  

 //方法:向链表中添加数据

 public void add(int data) {

  //判断链表为空的时候

  if (head == null) {

//如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点

  head = new Node(data);

  current = head;

  } else {

  //创建新的结点,放在当前节点的后面(把新的结点合链表进行关联)

  current.next = new Node(data);

  //把链表的当前索引向后移动一位

  current = current.next; //此步操作完成之后,current结点指向新添加的那个结点

  }

 }

  

  

 //方法重载:向链表中添加结点

 public void add(Node node) {

  if (node == null) {

  return;

  }

  if (head == null) {

  head = node;

  current = head;

  } else {

  current.next = node;

  current = current.next;

  }

 }

  

  

 //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历

 public void print(Node node) {

  if (node == null) {

  return;

  }

  

  current = node;

  while (current != null) {

  System.out.println(current.data);

  current = current.next;

  }

 }

  

 //方法:判断单链表是否有环。返回的结点是相遇的那个结点

 public Node hasCycle(Node head) {

  

  if (head == null) {

  return null;

  }

  

  Node first = head;

  Node second = head;

  

  while (second != null) {

  first = first.next;

  second = second.next.next;

  

  if (first == second) { //一旦两个指针相遇,说明链表是有环的

   return first; //将相遇的那个结点进行返回

  }

  }

  

  return null;

 }

  

 //方法:有环链表中,获取环的长度。参数node代表的是相遇的那个结点

 public int getCycleLength(Node node) {

  

  if (head == null) {

  return 0;

  }

  

  Node current = node;

  int length = 0;

  

  while (current != null) {

  current = current.next;

  length++;

  if (current == node) { //当current结点走到原点的时候

   return length;

  }

  }

  

  return length;

 }

  

 class Node {

  //注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。

  int data; //数据域

  Node next;//指针域

  

  public Node(int data) {

  this.data = data;

  }

 }

  

  

 public static void main(String[] args) {

  LinkList list1 = new LinkList();

  

  Node second = null; //把第二个结点记下来

  

  //向LinkList中添加数据

  for (int i = 0; i < 4; i++) {

  list1.add(i);

  if (i == 1) {

   second = list1.current; //把第二个结点记下来

  }

  }

  

  list1.add(second); //将尾结点指向链表的第二个结点,于是单链表就有环了,备注:此时得到的环的结构,是本节中图2的那种结构

  Node current = list1.hasCycle(list1.head); //获取相遇的那个结点

  

  System.out.println("环的长度为" + list1.getCycleLength(current));

 }

  

 }

 运行效果:

如果将上面的104至122行的测试代码改成下面这样的:(即:将图2中的结构改成图1中的结构)

1

2

3

4

5

6

7

8

9

10

11

12

13

public static void main(String[] args) {

  LinkList list1 = new LinkList();

  //向LinkList中添加数据

  for (int i = 0; i < 4; i++) {

   list1.add(i);

  }

 

  list1.add(list1.head); //将头结点添加到链表当中(将尾结点指向头结点),于是,单链表就有环了。备注:此时得到的这个环的结构,是本节中图1的那种结构。

 

  Node current = list1.hasCycle(list1.head);

 

  System.out.println("环的长度为" + list1.getCycleLength(current));

}

 

运行结果:

如果把上面的代码中的第8行删掉,那么这个链表就没有环了,于是运行的结果为0。

 

 

一,问题描述

请自己构造一个简单的有序单链表,然后实现删除链表中的重复结点。比如:

 

二,问题分析

首先要实现一个单链表,因此需要定义一个节点类Node。其次,实现向链表中添加结点的方法(使用尾插法)addNode

删除重复结点的实现思路:

定义两个指针:pre 和 next。初始时,pre指向链表中的第一个元素,next指向链表中的第二个元素。如果 pre 的值与 next 的值不相等,则两个指针分别都向后移一个结点;若相等,则删除 next 指针指向的结点即可。

 

三,整个代码实现

复制代码

// delete duplicated nodes in increased listpublic class MyLinkedList {    private class Node{        int ele;        Node next;        public Node(int ele) {            this.ele = ele;            next = null;        }    }        private Node head;    private Node tail;        //采用尾插法添加结点    public void addNode(int ele){        Node newNode = new Node(ele);        if(tail != null)            tail.next = newNode;        else{
// first node head = newNode; } tail = newNode; } //删除有序单链表中的重复结点 public void delDuplicatedNode(){ if(head == null) return; Node pre,next; pre = head; next = head.next; while(next != null) { if(pre.ele != next.ele) { pre = next; next = next.next; }else{
//delete next point node Node delNode = next; pre.next = next.next; next = next.next; delNode.next = null;//avoid memory leak// delNode = null; } } } @Override public String toString() { if(head == null) return "null"; Node current = head; StringBuilder sb = new StringBuilder(); while(current != null){ sb.append(current.ele + " "); current = current.next; } return sb.toString(); } //hapjin test public static void main(String[] args) { MyLinkedList mylist = new MyLinkedList(); int[] eles = {1,2,3,3,4,4,5}; for (int ele : eles) { mylist.addNode(ele); } System.out.println("before del: " + mylist); mylist.delDuplicatedNode(); System.out.println("after del: " + mylist); }}

转载于:https://my.oschina.net/u/2822116/blog/789360

你可能感兴趣的文章
明略数据首发"明智系统" 吴明辉:从个体附能到全局智能
查看>>
云计算带给SaaS的机遇
查看>>
掘金全球最大商务差旅市场 SAP旗下Concur联合中数通进军中国
查看>>
《PIC微控制器项目设计:C语言》一1.3 总结
查看>>
程序员父亲的遗产——编程十诫
查看>>
不可不知的NAT网关的防火墙功能
查看>>
《机械制造业智能工厂规划设计》——2.2 智能工厂设计需求分析
查看>>
关于Vue.js的响应式原理
查看>>
云运维管理专项评估标准将在可信云大会推出
查看>>
大变样:Firefox新一代UI “Photon”设计曝光
查看>>
云计算、IoT和SDN为企业网带来最大的问题
查看>>
云趋势下的Windows平台:生存并快乐着
查看>>
不要再在JavaScript中写 CSS了
查看>>
Gartner:云安全进入高速发展期
查看>>
云存储能否成为数据安全灵药?几个角度全方位剖析
查看>>
未来几年SDN将进一步提升云服务利润率
查看>>
手把手教你用 Python 和 Scikit-Learn 实现垃圾邮件过滤
查看>>
Hinton亲自讲解迄今未发表工作:胶囊理论的核心概念到底是什么?
查看>>
公开课总结发布《云数据库实现原理和海量运维方法》
查看>>
预告:如何完成从学术科研到产业创新的华丽转身?| 硬创公开课
查看>>