单链表的常见操作
最近在学习数据结构中单链表的常见操作,用java实现了以下几个操作:
- 单链表的增删改查
- 统计单链表的有效节点个数
- 查找单链表中的倒数第k个节点
- 单链表的反转
- 从尾到头打印单链表
- 合并两个有序的单链表,合并之后的链表依然有序
有些地方考虑不周。有错误的地方还望指出,我会及时改正。
具体的代码实现如下:
package com.LinkedList.summary;
import java.util.Stack;
public class SingleLinkedListTest {
public static void main(String[] args) {
// 创建节点
Node node1 = new Node(1, "A");
Node node2 = new Node(2, "B");
Node node3 = new Node(3, "C");
Node node4 = new Node(4, "D");
Node node5 = new Node(5, "E");
Node node6 = new Node(6, "F");
Node node7 = new Node(7, "G");
// 创建单链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 添加操作
System.out.println("添加前的单链表:");
singleLinkedList.list();
singleLinkedList.addByOrder(node1);
singleLinkedList.addByOrder(node3);
singleLinkedList.addByOrder(node4);
singleLinkedList.addByOrder(node2);
singleLinkedList.addByOrder(node1);
System.out.println("添加后的单链表:");
singleLinkedList.list();
//修改操作
singleLinkedList.updateByNo(new Node(2,"BB"));
System.out.println("修改后的单链表");
singleLinkedList.list();
//删除操作
singleLinkedList.deleteByNo(2);
System.out.println("删除后的单链表");
singleLinkedList.list();
//统计单链表的有效个数
System.out.println("单链表个数为:" + getLength(singleLinkedList));
//查找单链表中的倒数第k个节点
System.out.println("查找单链表中的倒数第2个节点");
System.out.println(findLastIndexNode2(singleLinkedList, 2));
//反转单链表
reverseLinkedList(singleLinkedList);
System.out.println("反转后的单链表");
singleLinkedList.list();
//逆序打印单链表
System.out.println("逆序打印单链表");
reversePrint(singleLinkedList);
//合并两个有序的单链表
System.out.println("合并单链表操作");
SingleLinkedList list1 = new SingleLinkedList();
list1.addByOrder(node1);
list1.addByOrder(node3);
list1.addByOrder(node5);
list1.addByOrder(node7);
list1.list();
SingleLinkedList list2 = new SingleLinkedList();
list2.addByOrder(node2);
list2.addByOrder(node4);
list2.addByOrder(node6);
System.out.println();
list2.list();
System.out.println("合并单链表");
mergeLinkedListByOrder(list1, list2).list();
}
/**
* 获取单链表有效节点的个数(如果是带头节点的链表,不统计头节点)
* @param head
* @return
*/
public static int getLength(SingleLinkedList singleLinkedList) {
// 判断链表是否为空
if (singleLinkedList.getHead().next == null) {
return 0;
}
// 定义一个变量用于计数
int count = 0;
Node temp = singleLinkedList.getHead();
while (temp.next != null) {
count++;
temp = temp.next;
}
return count;
}
/**
* 查找单链表中的倒数第k个节点
* 1.先得到单链表的长度size 2.再找到size-index个节点就是倒数第index个节点
* @param head
* @return
*/
public static Node findLastIndexNode2(SingleLinkedList singleLinkedList, int index) {
// 获取链表的长度
int length = getLength(singleLinkedList);
if (index > length || index < 0) {
return null;
}
Node temp = singleLinkedList.getHead().next;
for (int i = 0; i < length - index; i++) {
temp = temp.next;
}
return temp;
}
/**
* 反转单链表
* 1.定义一个新的头节点
* 2.遍历单链表,每次遍历一个就取一个节点放在新的头节点的最前端,相当于插入操作
* 3.将原来单链表的头节点指向最后一个插入的节点
* @param singleLinkedList
* @return
*/
public static SingleLinkedList reverseLinkedList(SingleLinkedList singleLinkedList) {
//判断单链表是否为空
if(singleLinkedList.getHead().next==null) {
return null;
}
Node newHead=new Node(0, "");//定义一个新的头节点
Node temp=singleLinkedList.getHead().next;//遍历辅助指针,从头节点的后的第一个节点开始
Node next =null;//用来存放当前节点的下一节点
while(temp!=null) {
next=temp.next;//先将下一节点存起来,因为一会下一节点要发生变化
temp.next=newHead.next;//将当前节点指向新头节点后面的第一个节点,第一次指向的时候,新的头节点后面没有节点,所以就指向了null
newHead.next=temp;//将新头节点指向当前节点
temp=next;//当前节点后移
}
//最后将原来的头节点指向新头节点的下一节点即可
singleLinkedList.getHead().next=newHead.next;
return singleLinkedList;
}
/**
* 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现单链表的逆序打印
*
* @param head
*/
public static void reversePrint(SingleLinkedList singleLinkedList) {
if (singleLinkedList.getHead().next == null) {
return;// 空链表不能打印
}
// 创建栈,将各个节点压入栈中
Stack<Node> stack = new Stack<Node>();
Node temp = singleLinkedList.getHead().next;
// 将链表中所有节点压入栈中
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
// 将栈的节点进行打印
while (stack.size() > 0) {
System.out.println(stack.pop());// 出栈,先进后出
}
}
/**
* 合并两个有序的单链表,合并之后的链表依然有序
* 1.首先需要创建一个新的单链表用来存放数据
* 2.复制其中一个单链表,然后从另外一个单链表中每取出一个数据就按顺序插入到新的单链表中
*
* @param list1
* @param list2
* @return
*/
public static SingleLinkedList mergeLinkedListByOrder(SingleLinkedList list1,SingleLinkedList list2) {
if(list1==null&&list2==null) {
return null;
}
if(list1==null&&list2!=null) {
return list2;
}
if(list1!=null&&list2==null) {
return list1;
}
//创建一个新的单链表
SingleLinkedList newSingleLinkedList = new SingleLinkedList();
//复制其中一个单链表
newSingleLinkedList=list1;
//遍历第二个单链表,每遍历一个数据,就按顺序插入到新的单链表中
Node temp=list2.getHead().next;
while(true) {
if(temp==null) {
break;
}
add(newSingleLinkedList,new Node(temp.no, temp.name));
temp=temp.next;
}
return newSingleLinkedList;
}
/**
* 根据传入的链表按顺序向链表中插入数据
* @param newSingleLinkedList
* @param node
*/
public static void add(SingleLinkedList newSingleLinkedList,Node node) {
// 因为头节点不能动,所以需要一个辅助指针进行遍历,找到待插入的位置
// 因为是单链表,通过每个几点可以知道下一个节点是谁,但是无法知道上一个节点是谁,所以这个需要找到的这个待插入位置应该是在前一个节点处
Node temp = newSingleLinkedList.getHead();
boolean flag = false;// 用来标记,下面会用到
while (true) {
// 判断跳出循环的条件,有几种情况
// 1.当遍历完也没找到待插入的位置,那就说明这个待插入的节点编号最大,插入到链表最后即可
if (temp.next == null) {// 这里说明遍历到链表最后了
break;
}
// 2.如果发现有相同的编号,那就不让这个数据插入,并给出提示有冲突了
// 由于这种情况比较特殊,是不让插入的情况,所以需要区分开,用一个flag来标记一下
if (temp.next.no == node.no) {
flag = true;
break;
}
// 3.待插入的位置在链表中间,这个时候需要判断了
// 如果待插入的数据的编号小于当前指针的下一个节点的编号,那就说明找到待插入节点的位置了,就在当前指针temp的位置后面进行插入即可
if (temp.next.no > node.no) {
break;
}
// 每次需要后移指针,才能遍历
temp = temp.next;
}
// 循环结束时,需要根据三种情况进行判断了
if (flag) {// 1.如果有相同编号,不插入,给提示
System.out.printf("待插入节点的编号%d已经存在,无法插入!", node.no);
} else {// 2.可以插入了
// 首先,需要将待插入数据的next指向temp的下一个节点
node.next = temp.next;
// 然后将temp的next指向待插入数据
temp.next = node;
}
}
}
/**
* 定义一个单链表操作类
*
*/
class SingleLinkedList {
// 定义头节点
private Node head = new Node(0, "");
public Node getHead() {
return head;
}
/**
* 按编号从小到大进行顺序添加操作
*
* @param node
*/
public void addByOrder(Node node) {
// 因为头节点不能动,所以需要一个辅助指针进行遍历,找到待插入的位置
// 因为是单链表,通过每个几点可以知道下一个节点是谁,但是无法知道上一个节点是谁,所以这个需要找到的这个待插入位置应该是在前一个节点处
Node temp = head;
boolean flag = false;// 用来标记,下面会用到
while (true) {
// 判断跳出循环的条件,有几种情况
// 1.当遍历完也没找到待插入的位置,那就说明这个待插入的节点编号最大,插入到链表最后即可
if (temp.next == null) {// 这里说明遍历到链表最后了
break;
}
// 2.如果发现有相同的编号,那就不让这个数据插入,并给出提示有冲突了
// 由于这种情况比较特殊,是不让插入的情况,所以需要区分开,用一个flag来标记一下
if (temp.next.no == node.no) {
flag = true;
break;
}
// 3.待插入的位置在链表中间,这个时候需要判断了
// 如果待插入的数据的编号小于当前指针的下一个节点的编号,那就说明找到待插入节点的位置了,就在当前指针temp的位置后面进行插入即可
if (temp.next.no > node.no) {
break;
}
// 每次需要后移指针,才能遍历
temp = temp.next;
}
if (flag) {// 1.如果有相同编号,不插入,给提示
System.out.printf("待插入节点的编号%d已经存在,无法插入!", node.no);
} else {// 2.可以插入了
// 首先,需要将待插入数据的next指向temp的下一个节点
node.next = temp.next;
// 然后将temp的next指向待插入数据
temp.next = node;
}
}
/**
* 根据编号删除节点
* @param no
*/
public void deleteByNo(int no) {
//首先判断链表是否为空
if(getHead().next==null) {
System.out.println("链表为空,无法删除!");
return;
}
//找到要删除的节点
Node temp=head;
boolean flag=false;
while(true) {
//如果找到最后,那说明没找到
if(temp.next==null) {
break;
}
//根据编号找到了
if(temp.next.no==no) {
flag=true;
break;
}
temp=temp.next;
}
if(flag) {
//把要删除节点的前一个节点的next指向要删除节点的后一个节点就行啦
temp.next=temp.next.next;
}else {
System.out.println("没有找到,无法删除!");
}
}
/**
* 根据编号修改节点中的数据
* @param node
*/
public void updateByNo(Node node) {
//首先需要判断链表是否为空
if(getHead().next==null) {
System.out.println("链表为空,无法修改!");
return;
}
//首先要找到要修改的节点
Node temp=head;
boolean flag=false;//由于两种情况不一样,所以标记一下
while(true) {
//1.如果找到最后了,那说明没找到
if(temp.next==null) {
break;
}
//2.找到相同编号了
if(temp.next.no==node.no) {//注意:这里如果temp是从头节点开始,那找到的节点是temp.next;如果temp从head.next开始,那找到的节点是temp
flag=true;
break;
}
temp=temp.next;
}
if(flag) {
temp.next.name=node.name;
}else {
System.out.printf("没有找到编号为%d的节点",node.no);
}
}
/**
* 遍历
*/
public void list() {
// 首先判断单链表是否为空
if (getHead().next == null) {
System.out.println("单链表为空,无法遍历!");
return;
}
// 如果不为空,由于头节点是不能动的,所以需要一个辅助指针用来遍历
Node temp = head.next;// 因为头节点不用遍历,所以指针从头节点的下一个节点开始即可
while (true) {
// 如果遍历到最后,那就跳出循环
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
/**
* 定义给一个节点类
*
*/
class Node {
public int no;
public String name;
public Node next;// 指向下一节点
// 构造器
public Node(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node [no=" + no + ", name=" + name + "]";
}
}