单链表的创建和遍历、求单链表中节点的个数、查找单链表中的中间结点、判断单链表是否有环、取出有环链表中环的长度,删除有序链表中的重复结点...

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 list
public 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值