文章目录
参考: https://mp.weixin.qq.com/s/Yls1ZtDxUkKUKEHnltVTIw
一、基本含义
1.1 线性存储结构
1、数组和链表都是线性存储结构的基础。
2、栈和队列都是线性存储结构的应用。
1.2 数组和链表
1.2.1 数组
数组是一种连续存储线性结构,元素类型相同,大小相等
数组的优点:存取速度快。
数组的缺点:
1、事先必须知道数组的长度。
2、插入删除元素很慢,效率很低
3、空间通常是有限制的。
4、需要大块连续的内存块。
1.2.2 链表
链表是离散存储线性结构。
n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。
链表优点:
1、空间没有限制。
2、插入删除元素很快。
链表缺点:存取速度很慢。
关键词 | 含义 |
---|---|
头指针 | 指向第一个节点的指针。 |
头节点 | 第一个节点,但无数据。 |
首节点 | 第二个节点,第一个有数据的节点。 |
尾节点 | 最后一个节点,最后一个有数据的节点。 |
尾指针 | 指向最后一个节点的指针。 |
指针域 | 指向一个节点,节点中指针域指向的就是一个节点了 |
“指针”与“节点”的关系,类似于“引用”与“对象”的关系。
头指针和头节点的区别:
头指针:
——头指针是指链表指向第一个结点的指针,若链表有头节点,则是指向头节点的指针。
——头指针具有标识作用,所以头指针冠以链表的名字(指针变量的名字)
——无论链表是否为空,头指针均不为空
——头指针是链表的必要元素
头节点:
——头节点是为了操作的统一和方便而设立的,放在第一个元素的节点之前,其数据域一般无意义(但也可以用来存放链表的长度)
——有了头节点,对在第一元素节点前插入节点和删除第一节点起操作与其它节点的操作就统一了
——头节点不一定是链表的必要元素
注意:确定一个链表我们只需要头指针,通过头指针就可以把整个链表都能推导出来了。
链表分类 | 含义 |
---|---|
单向链表 | 一个节点指向下一个节点。 |
双向链表 | 一个节点有两个指针域。 |
循环链表 | 能通过任何一个节点找到其他所有的节点,将两种(双向/单向)链表的最后一个结点指向第一个结点从而实现循环。 |
二 、链表操作
2.1 创建节点类
首先,定义节点类。
public class Node {
private int value;//数据域
private Node nextNode; // 指针域,指向下一个节点
public Node() {
}
public Node(int value) {
this.value = value;
}
public Node(int value, Node nextNode) {
this.value = value;
this.nextNode = nextNode;
}
}
2.2 获取
2.2.1 链表的长度
注意:链表的长度 = 链表的节点数 - 1。
已知:头节点。
public static int getLength(Node headNode) {
int length;
Node temp = head.getNextNode(); //临时节点,从首节点开始
// 找到尾节点
while (temp != null) {
length++;
temp = temp.getNextNode();
}
return length;
}
2.2.2 遍历链表
已知:头节点。
// 遍历链表。从前向后。
public static void traverse(Node headNode) {
Node temp = headNode.getNextNode();//首节点
while (temp != null) {
System.out.println("当前节点的值:" + temp.getValue());
temp = temp.getNextNode(); //继续下一个
}
}
// 遍历链表。从后向前。
public static void traverse(Node headNode) {
if (headNode != null) {
traverse(headNode.getNextNode());
System.out.println(headNode.getData());
}
}
2.2.3 倒数第k个节点
已知:头节点,倒数的节点索引值。
思路:设置两个指针p1、p2,让p2比p1快k个节点,同时向后遍历,当p2为空,则p1为倒数第k个节点
public static Node get(Node headNode, int k) {
if (k < 1 || k > getLength(headNode)){
return null;
}
Node p1 = headNode;
Node p2 = headNode;
for (int i = 0; i < k - 1; i++){
p2 = p2.getNextNode();
}
// 只要p2为null,那么p1就是倒数第k个节点了
while (p2.getNextNode() != null) {
p2 = p2.getNextNode();
p1 = p1.getNextNode();
}
}
2.2.4 中间节点
已知:头节点。
思路:一个每次走1步,一个每次走两步,走两步的遍历完,然后走一步的指针,那就是中间节点
public static Node searchMid(Node headNode) {
Node p1 = headNode;
Node p2 = headNode;
while (p2 != null && p2.getNextNode() != null && p2.getNextNode().getNextNode() != null) {
p1 = p1.getNextNode();
p2 = p2.getNextNode().getNextNode();
}
return p1;
}
2.3 增加
2.3.1 尾部增加
已知:头节点,增加元素的值。
// 链表尾部增加节点。
public static void add(Node headNode,int value) {
Node newNode = new Node(value);
Node temp = headNode.getNextNode(); //首节点
// 找到尾节点
while (temp != null) {
temp = temp.getNextNode();
}
temp.getNextNode() = newNode;
}
2.3.2 插入节点
已知:头节点,插入位置的索引,增加元素的值。
public static void insert(Node headNode, int index, int value) {
//首先需要判断指定位置是否合法,
if (index < 1 || index > getLength(headNode) + 1) {
return;
}
Node temp = headNode.getNextNode();// 首节点
int currentPos = 0;//记录遍历的当前位置
Node insertNode = new Node(value);//初始化要插入的节点
while (temp != null) {
if (index == currentPos) {
// 交换指向
insertNode.getNextNode() = temp;
temp.getNextNode() = insertNode;
return;
}
currentPos++;
temp = temp.getNextNode();
}
}
2.4 删除
2.4.1 指定索引
已知:头节点、删除节点的索引。
public static void delete(Node headNode, int index) {
if (index < 1 || index > getLength(headNode) + 1) {
return;
}
Node temp = headNode.getNextNode(); // 首节点
int currentPos = 0;
while (temp != null) {
if (index == currentPos) {
// 交换指向。
Node deleteNode = temp.getNextNode();
temp = deleteNode; // 删除。
return;
}
currentPos++;
temp = temp.getNextNode();
}
}
2.4.2 删除重复数据
已知:头节点。
public static void delete(Node headNode) {
Node temp = headNode.getNextNode(); // 首节点。
Node nextNode = temp.getNextNode();
while (temp != null) {
while (nextNode != null) {
if (temp.getData() == nextNode.getData()) {
temp = nextNode;
} else {
nextNode = nextNode.getNextNode();
}
}
temp = temp.getNextNode();
}
}
2.5 排序
已知:头节点。
public static void sort(Node headNode) {
for (Node currentNode = headNode.getNextNode(); currentNode.getNextNode()!= null; currentNode = currentNode.getNextNode()) {
for (Node nextNode = headNode.getNextNode(); nextNode.getNextNode() != null; nextNode = nextNode.getNextNode()) {
if (nextNode.getData() > nextNode.getNextNode().getData()) {
int temp = nextNode.getData();
nextNode.getData() = nextNode.getNextNode().getData();
nextNode.getNextNode().getData() = temp;
}
}
}
}
2.6 反转
已知:头节点。
public static Node reverse(Node headNode) {
Node prev ;
if (headNode == null || headNode.getNextNode() == null) {
prev = headNode;
} else {
Node temp = reverse(headNode.getNextNode());
headNode.getNextNode().getNextNode() = headNode;
headNode.getNextNode() = null;
prev = temp;
}
return prev;
}