链表
- 链表以节点的方式存储数据,特点是:线性结构、链式存储。
- 每个节点包含 data域 和 next域,data域保存数据,next域用来指向下一节点。
- 链表的各个节点在内存中不一定是连续存储。
- 链表分带头节点的链表和不带头节点的链表,头节点指向链表中第一个数据节点。
单链表类中实现方法
void add(Node n)
:将节点n添加到链表尾。在遍历过程中,可以对temp.next进行判断。void addByOrder(Node n)
:假设链表中 节点 按照 id 从小到大的顺序进行排列,将节点n按照id大小插入到链表的对应位置。在遍历过程中,必须对temp.next进行判断,因为需要使用对应位置的前一个位置上的节点。void update(Node n)
:将链表中与节点n相同id的节点 的数据域中的信息变为节点n数据域中的信息。在遍历过程中,可以对temp进行判断。最好首先判断链表是否为空。void delete(int nodeId)
:删除链表中id为参数nodeId的的节点。在遍历过程中,必须对temp.next进行判断,因为需要使用对应位置的前一个位置上的节点。void printList()
:打印链表。int length()
:返回链表长度。最好首先判断链表是否为空。Node getNodeFromBack(int k)
:获取链表中倒数第k个节点。思路是:获取链表长度Len,倒数第k个节点就是正数第(Len-k+1)个节点,然后遍历找到第(Len-k+1)个节点。最好首先判断链表是否为空。Node getNodeFromBackTwo(int k)
:通过遍历一次链表获取链表中倒数第k个节点。思路:使用两个辅助节点,头一个辅助节点先向后遍历k个节点后,第二个辅助节点开始向后遍历链表,这样当第一个辅助节点为空时,第二个辅助节点所在的位置就是倒数第k个节点所在的位置。void reverse()
:反转单链表。创建一个新链表,从头开始提取每一个节点,每次都将节点添加到新链表的头部,然后让旧链表的头节点指向此新链表的第一个节点。void reversePrint()
:逆序打印链表,不改变链表结构。使用了栈。
merge函数
static SingleLinkedList merge(SingleLinkedList listOne, SingleLinkedList listTwo)
:合并两个有序链表,假设listOne和listTwo都是有序链表,且顺序为id从小到大。
代码
代码
import java.util.Stack;
/**
* 实现单链表,测试其使用
* @author dxt
*
*/
public class SingleLinkedListDemo {
/**
* 合并两个有序链表,假设listOne和listTwo都是有序链表,且顺序为id从小到大
* @param listOne
* @param listTwo
* @return
*/
public static SingleLinkedList merge(SingleLinkedList listOne, SingleLinkedList listTwo) {
//1.判断上述两个链表是否为空。如果全为空,那么返回的是null
if(listOne.getHeadNode().next == null) {
return listTwo;
}
if(listTwo.getHeadNode().next == null) {
return listOne;
}
//2. 合并
SingleLinkedList result = new SingleLinkedList(); //最终合并后的结果
Node tempNodeOne = listOne.getHeadNode().next; //初始为链表1的第一个节点
Node tempNodeTwo = listTwo.getHeadNode().next; //初始为链表2的第一个节点
Node resultNode = result.getHeadNode();
while(true) {
//第一个链表已遍历完,将第二个链表剩余节点链接到结果链表后即可
if(tempNodeOne == null) {
resultNode.next = tempNodeTwo;
break;
}
//第二个链表已遍历完,将第一个链表剩余节点链接到结果链表即可
if(tempNodeTwo == null) {
resultNode.next = tempNodeOne;
break;
}
//当两个链表都有节点时,选择当前id小的节点加入到链表中(没有对id相同情况作特殊处理)
if(tempNodeOne != null && tempNodeTwo != null) {
if(tempNodeOne.id < tempNodeTwo.id) {
resultNode.next = tempNodeOne;
tempNodeOne = tempNodeOne.next;
}else {
resultNode.next = tempNodeTwo;
tempNodeTwo = tempNodeTwo.next;
}
resultNode = resultNode.next; //结果链表后移
}
}
return result;
}
public static void main(String[] args) {
//测试merge()方法是否正确
SingleLinkedList sll1 = new SingleLinkedList();
SingleLinkedList sll2 = new SingleLinkedList();
Node n1 = new Node(1, "张三");
Node n2 = new Node(2, "李四");
Node n3 = new Node(3, "王五");
Node n4 = new Node(4, "喜羊羊");
Node n5 = new Node(5, "灰大狼");
Node n6 = new Node(6, "懒洋洋");
sll1.add(n1);
sll1.add(n2);
sll1.add(n5);
sll2.add(n3);
sll2.add(n4);
sll2.add(n6);
SingleLinkedList sll3 = merge(sll1, sll2);
sll3.printList();
System.out.println("##########美丽分界线###########");
//测试 SingleLinkedList类中方法是否正确
//1. 声明一个链表对象
SingleLinkedList sll = new SingleLinkedList();
//2. 准备测试节点
Node nn1 = new Node(1, "张三");
Node nn2 = new Node(2, "李四");
Node nn3 = new Node(3, "王五");
//3. 基础测试
sll.printList();
sll.add(nn1);
sll.add(nn3);
sll.addByOrder(nn2);
sll.printList();
sll.reverse();
sll.reversePrint();
sll.printList();
Node n = new Node(1, "灰太狼");
sll.update(n);
sll.printList();
System.out.println(sll.length());
System.out.println(sll.getNodeFromBack(1));
System.out.println(sll.getNodeFromBackTwo(1));
System.out.println(sll.getNodeFromBack(2));
System.out.println(sll.getNodeFromBackTwo(2));
System.out.println(sll.getNodeFromBack(3));
System.out.println(sll.getNodeFromBackTwo(3));
}
}
/**
* 节点类,每个节点包含 数据域 和 next域,数据域中的id属性来唯一标识一个节点
* @author dxt
*
*/
class Node{
public int id;
public String name; //节点数据内容
public Node next; //指向下一个节点
//构造器
public Node(int id, String name) {
this.id = id;
this.name = name;
}
//重写toString()方法
@Override
public String toString() {
return "Node [id=" +this.id+ ", name=" +this.name+ "]"; //拼接一个字符串
}
}
/**
* 单链表实现类
* @author dxt
*
*/
class SingleLinkedList{
//初始化一个头节点,头节点不存放任何数据,
private Node headNode = new Node(0, "");
/**
* 获取链表头节点
* @return
*/
public Node getHeadNode() {
return headNode;
}
/**
* 添加节点到链表中
* @param node
*/
public void add(Node node) {
//1. 定位到链表最后,需要使用一个辅助节点,且要对temp.next进行判断
Node temp = headNode;
while(true) {
if(temp.next == null) {
break;
}
temp = temp.next;
}
//2. 将此节点添加到链表尾
temp.next = node;
}
/**
* 假设链表中 节点 按照 id从小到大的顺序进行排列,将node节点按照id插入到链表的对应位置
* @param node
*/
public void addByOrder(Node node) {
//1. 定位到插入位置的前一个节点,需要一个辅助节点,需要对temp.next进行判断
Node temp = headNode;
boolean flag = false; //判断是否会出现 新加入节点与链表中某一结点存在相同id问题
while(true) {
//已到链表尾,说明node节点的id是最大的 或 链表为空
if(temp.next == null) {
break;
}
//temp下一个节点的id大于node节点的id,则temp就是对应位置的前一个节点
if(temp.next.id > node.id) {
break;
}else if(temp.next.id == node.id) { //出现问题,因为我们定义id是唯一的
flag = true; //存在新加入节点 与 链表中节点为统一id的问题
break;
}
temp = temp.next; //后移
}
if(flag) {
System.out.println("链表中已存在对应节点。");
}else { //将node插入到链表中
node.next = temp.next; //注意两条语句的顺序
temp.next = node;
}
}
/**
* 更新节点。将链表中与node节点相同id的节点 的数据域中的信息变为node节点数据域中的信息
* @param node
*/
public void update(Node node) {
//1. 判断链表是否为空
if(headNode.next == null) {
System.out.println("链表为空, 更新失败。");
return;
}
//2. 找到对应节点
Node temp = headNode.next; //已确定链表至少有一个节点,注意是对temp进行判断
boolean flag = false; //表示是否找到该节点,默认没找到
while(true) {
if(temp == null) { //遍历完整个链表,没有找到
break;
}
if(temp.id == node.id) { //找到
flag = true;
break;
}
temp = temp.next;
}
//3. 更改对应数据
if(flag) { //找到
temp.name = node.name;
}else {
System.out.println("未找到对应节点,更新失败。");
}
}
/**
* 删除链表中id为参数nodeId的的节点
* @param no
*/
public void delete(int nodeId) {
Node temp = headNode;
boolean flag = false; //标志是否找到要删除的节点
while(true) {
if(temp.next == null) { //遍历完链表,没有找到
break;
}
if(temp.next.id == nodeId) { //定位到要删除节点的前一个节点
flag = true;
break;
}
temp = temp.next; //后移
}
if(flag) { //找到
temp.next = temp.next.next;
}else {
System.out.println("未找到对应节点,删除失败。");
}
}
/**
* 打印链表
*/
public void printList() {
//判断链表是否为空
if(headNode.next == null) {
System.out.println("链表为空");
return;
}
//开始遍历
Node temp = headNode.next; //链表不为空,至少有一个节点
while(temp != null) { //我一定要写一下这种遍历方式
System.out.println(temp);
temp = temp.next; //后移
}
}
/**
* 返回链表中有效节点的个数(不包含头节点)
* @return
*/
public int length() {
//如果为空,直接返回0
if(headNode.next == null) {
return 0;
}
Node temp = headNode.next;
int len = 0;
while(temp != null) {
len++;
temp = temp.next;
}
return len;
}
/**
* 获取链表中倒数第k个节点
* 思路:获取链表长度Len,倒数第k个节点就是正数第(Len-k+1)个节点,然后遍历找到第(Len-k+1)个节点
* @param k
* @return
*/
public Node getNodeFromBack(int k) {
//1. 判断链表是否为空
if(headNode.next == null) {
return null;
}
//2.1 获取链表总长度,(此过程会进行一次遍历)
int len = this.length();
//2.2 校验一下参数,看是否合法
if(k <= 0 || k > len) {
throw new RuntimeException("参数越界,发生错误");
}
//3. 定位到倒数第k个元素
int index = len - k + 1;
//4. 进行第2次遍历查找
Node temp = headNode.next;
for(int i=0; i<index-1; i++) { //要找到正数第index个元素,只需移动 index-1 次
temp = temp.next;
}
return temp;
}
/**
* 通过遍历一次链表获取链表中倒数第k个节点,效率更高
* 思路:使用两个辅助节点,头一个辅助节点先走k个节点的位置,然后第二个节点在开始向后遍历链表,这样当第一个节点
* 为空时,第二个节点所在的位置就是倒数第k个节点所在的位置。
* @param k
*/
public Node getNodeFromBackTwo(int k) {
//1. 判断链表是否为空
if(headNode.next == null) {
return null;
}
//2. 数据校验
if(k <= 0) {
throw new RuntimeException("参数越界,发生错误");
}
//3.先让第一个节点向前移动k个节点的位置
Node temp_fast = headNode.next; //走在前面的节点
Node temp_slow = headNode.next; //走在后面的节点
int i=0;
for(i=0; i<k && temp_fast!=null; i++) { //i从1开始
temp_fast = temp_fast.next;
}
//4. 判断参数k是否合理,防止链表中不足k个节点
if(i < k) { //链表中节点个数小于k个
throw new RuntimeException("参数越界,发生错误。");
}
//5. 然后两个节点在各自的位置以相同的速度向后遍历
while(temp_fast != null) {
temp_fast = temp_fast.next;
temp_slow = temp_slow.next;
}
return temp_slow;
}
/**
* 单链表的反转
*/
public void reverse() {
//1. 链表为空 或 链表中只有一个元素 则直接返回
if(headNode.next==null || headNode.next.next==null) {
return;
}
//2. 获得一个新的反转链表
Node cur = headNode.next; //辅助指针,用于遍历整个链表
Node next = headNode.next; //指向cur节点的下一个节点,否则cur无法遍历
Node newHead = new Node(0,""); //一个新的链表的头节点,cur每取到一个节点,都插入到此链表的头部
while(cur != null) {
next = cur.next; //保存当前节点的下一个节点
cur.next = newHead.next; //将当前节点作为新链表的第一个节点
newHead.next = cur; //新链表的头节点指向第一个节点
cur = next; //遍历旧链表的下一个节点
}
//3. 更改指向
headNode.next = newHead.next; //让headNode.next指向新的反转链表
}
/**
* 逆序打印单链表,使用了栈结构。不改变链表结构
*/
public void reversePrint() {
//1. 检查链表是否为空
if(headNode.next == null) {
return;
}
//2. 创建栈,并将链表节点从头到尾压入栈中
Stack<Node> stack = new Stack<Node>();
Node temp = headNode.next;
while(temp != null) {
stack.push(temp);
temp = temp.next;
}
//3. 打印
while(stack.size() > 0) {
System.out.println(stack.pop());
}
}
}
结果: