单链表 Single Linked List
链表是有序的列表,每个节点包括一个数据元素(data)和指向下一个节点的地址(next)
链表的各个节点不一定是连续存储在内存中
图示:
head节点不存放具体数据,作用是表示单链表表头
对于链表来说,最关键的是要定义节点:
class Node{
public int no;
public String content;
public Node next;
public Node(int no, String content) {
this.no = no;
this.content = content;
}
}
在上述例子中,节点的data域存放的是序号(no)和内容(content),next域存放的是下一个节点。
然后定义链表:链表首先需要定义头部(head)。如前面所说,head不要存放任何东西。
向链表中添加新元素:
- 当我们直接向链表中添加数据时,首先要检索到链表的最后一位,然后将当前最后一位的 next 从 null 指向新元素
- 当我们根据序号大小按顺序向链表中添加数据时,首先要检索新元素的合适位置。一般定义一个暂时变量temp,利用temp遍历整个链表来找出合适位置,最终temp将会定位到新元素合适位置的前一位。
利用
temp.next = node.next; temp.next = node
来完成插入
class SingleLinkedList{
public Node head = new Node(0, "");
// 直接添加
public void add(Node node) {
Node temp = head;
while(true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
// 有序添加
public void addByOrder(Node node) {
Node temp = head;
boolean flag = false; // 用来判断是否有相同编号的元素
while(true) {
if (temp.next == null) {
break;
}
if (temp.next.no > node.no) {
break;
}
else if (temp.next.no == node.no) {
flag = true;
break;
}
}
if (flag) {
System.out.println("序号已存在");
}
else {
temp.next = node.next;
temp = temp.next
}
}
}
在链表中修改元素:
通过检索元素的序号,找出对应元素,然后修改改元素信息,过程和有序添加类似。
在链表中删除元素:
- 通过检索找出待删除元素的位置,也需要利用temp来检索,最终temp会定位到待删除元素的前一位
- 利用 temp.next = temp.next.next 完成删除
- (no=2, a2)这个节点由于没有其它引用指向,会被垃圾回收机制回收
反转链表
遍历法
基本思路:遍历原链表,将每一个遍历的元素指针反转
但在过程中可能会出现链表断裂的问题,如下图所示:
当第二个节点指向改变时,链表在第二、三节点处断裂,无法再获取第三、四节点。因此,我们需要用一个变量来保存当前节点的下一节点。
一般需要三个指针,一个指向当前节点的前一个,定义为pre;一个指向当前节点,定义为cur;一个指向当前节点的下一节点,定义为next。
- 首先将 pre 和 next 初始化为 null, 将 cur 指向第一个节点(头节点的下一位)
- 保存 cur 的下一节点至 next
- 将 cur 指针反转,即指向前一位(pre)
- 将 pre 向后移动一位
- 将 cur 向后移动一位
- 重复 2-5步,直到 cur = null
- 最后返回 pre,需要注意这时的pre并不是头节点,还需要额外将 head.next 指向 pre,才是一个完整的反转链表
遍历法代码如下:
// 反转链表(遍历)
public static Node reverseHead(Node head) {
// 如果没有元素或只有一个元素,返回自己
if (head.next == null || head.next.next == null) {
return head;
}
// 第1步
Node pre = null; // cur的前一个节点
Node cur = head.next; // 当前遍历的节点
Node next = null; // cur的下一个节点
while (cur != null) {
next = cur.next; // 第2步
cur.next = pre; // 第3步
pre = cur; // 第4步
cur = next; // 第5步
}
return pre;
}
递归法
首先需要找到最后一个节点,然后从最后一个节点开始反转
递归法代码如下:
// 反转链表(递归)
// 传入的是第一个节点而非头节点
public static Node reverseHead2(Node node) {
// 先判断是否是空链表或是只有一个节点
// 同时,node.next还要判断是否检索到最后一个节点,如果是,开始返回
if (node == null || node.next == null) {
return node;
} else {
Node newHead = reverseHead2(node.next);
node.next.next = node;
node.next = null;
return newHead;
}
}
合并两个单链表
基本思路:利用两个temp遍历,不断比较temp变量所指的节点,将较小的先添加到合并链表中
遍历法
遍历法代码如下(需要改进):
// 合并两个单链表(遍历)
public static Node merge(Node head1, Node head2) {
Node newHead = new Node(0, "New Head");
Node temp = newHead;
head1 = head1.next;
head2 = head2.next;
// 判断是否两个链表都为空
if (head1 == null && head2 == null) {
return newHead;
}
//开始比较
while(true) {
if ( head1 == null || head2 == null) {
break;
}
if (head1.no <= head2.no) {
temp.next = head1;
temp = temp.next;
head1 = head1.next;
}else {
temp.next = head2;
temp = temp.next;
head2 = head2.next;
}
}
//当一方已经完成遍历,将另一方的剩余节点全部加入合并链表
if (head1 == null) {
temp.next = head2;
}else {
temp.next = head1;
}
return newHead;
}
递归法
待补充…
相关章节
第一节 简述
第二节 稀疏数组 Sparse Array
第三节 队列 Queue
第四节 单链表 Single Linked List
第五节 双向链表 Double Linked List
第六节 单向环形链表 Circular Linked List
第七节 栈 Stack
第八节 递归 Recursion
第九节 时间复杂度 Time Complexity
第十节 排序算法 Sort Algorithm
第十一节 冒泡排序 Bubble Sort
第十二节 选择排序 Select Sort
第十三节 插入排序 Insertion Sort
第十四节 冒泡排序,选择排序和插入排序的总结
第十五节 希尔排序 Shell’s Sort
第十六节 快速排序 Quick Sort
第十七节 归并排序 Merge Sort