链表一般分为单向链表、双向链表、循环链表,单向链表是比较简单的一个。
单向链表
单向链表,顾名思义就是除了链表的最后一个结点,其他的每一个都保存着下一个结点的地址
单向链表节点结构
class SingleNode{
//这里使用public主要是想省略set/get方法,实际上不可以这样做的
public int no;//结点编号
public String name;//结点名称
public SingleNode next;//指向下一个节点
//构造器
public SingleNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "SingleNode{" +
"no=" + no +
", name='" + name +
'}';
}
}
单向链表的实现
class SingleLinkedList{
//初始化一个头节点,头节点不需要动
private SingleNode head = new SingleNode(0,"");
//返回头节点
public SingleNode getHead(){
return this.head;
}
/**
* 基本方法
*/
//统计单链表的有效节点个数
public static int getLength(SingleNode head){
SingleNode temp = head;
int count = 0;
while (true){
if (temp.next == null){
break;
}
count ++;
temp = temp.next;
}
return count;
}
/**
* 添加节点到单向链表
* 思路:不考虑编号顺序
* 1、找到当前链表的最后一个节点
* 2、将最后这个节点的next指针指向新的节点
*/
public void add(SingleNode node){
//因为头节点不能移动,因此我们需要一个辅助来遍历链表
SingleNode temp = head;
//遍历链表,找到最后一个
while (true){
if (temp.next == null ){
break;
}
//如果没有到最后,将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向链表的最后
//最后将这个节点的next指向新节点
temp.next = node;
}
/**
* 添加节点时,将节点按顺序添加到指定的节点
* 如果有这个序号的节点,则会添加失败,并提示失败信息
*/
public void addByOrder(SingleNode node){
//因为头节点不能移动,因此我们还是需要一个辅助变量temp来帮助我们添加到指定的位置
//单链表需要将temp指向添加位置的前面一个元素,才可以添加,不然添加不了
SingleNode temp = head;
//标志添加的节点是否存在,默认为false,默认不存在
boolean flag = false;
while (true){
//说明temp已经在链表的最后了
if (temp.next == null){
break;
}
//如果当前节点的下一个(next)节点的no的大于所要添加的节点,那么位置就找到了
if (temp.next.no > node.no){
break;
//添加的节点编号重复了
}else if (temp.next.no == node.no){
flag = true;
break;
}
//后移,遍历链表
temp = temp.next;
}
if (flag){
System.out.println("编号已经存在,无法加入,重复编号信息:"+
node+"与之冲突的编号节点信息:"+temp.next);
}else {//插入节点
node.next = temp.next;
temp.next = node;
}
}
//修改节点的信息,根据编号来修改,no编号不能被修改
public void update(SingleNode newNode){
//判断是否为空
if(head.next == null){
System.out.println("链表为空");
}
//不为空,找到想要修改的节点(根据编号no)
//其实这里可以是temp=head,但是没意义,因为head不可能大于null,我们初始化的时候就给head赋值了
SingleNode temp = head.next;
boolean flag = false;//节点是否存在
while (true){
//这里不能temp.next,更新数据想要查到最后一个,如果temp.next,那么它只要判断下一个是null就会退出,从而不会判断no是否相等
//而且如果这里是temp.next,那么temp开始就不能赋值为head.next,不然会空指针异常
if (temp == null){
break;
}
if (temp.no == newNode.no){
flag = true;
break;
}
temp = temp.next;
}
//根据false是否找到节点
if (flag){//找到了
temp.name = newNode.name;
}else {//没有找到
System.out.println("没有找到与之对应编号的节点,无法修改");
}
}
//删除节点,想要找到想要删除的节点的前一个节点,才能把它删除
//还是那样,head不能动,想要一个辅助变量temp
public void delete(int no){
SingleNode 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){
temp.next = temp.next.next;
}else {
System.out.println("删除的节点不存在,编号:"+no);
}
}
//显示链表中的所有消息
public void list(){
if (head.next == null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助变量来遍历
SingleNode temp = head;
while (true){
if (temp.next == null){
break;
}
//输出节点的信息
System.out.println(temp.next);
//将temp后移,一定要后移
temp = temp.next;
}
}
//=============================拓展方法(面试)====================================
//查找单链表中倒数第k个节点
public SingleNode findLastIndexNode(int i,SingleNode head){
int count = getLength(head);
int num = count-i;
if (head.next == null){
return null;
}
if (num<0){
return null;
}
SingleNode temp = head.next;
for (int j = 0; j < num; j++) {
temp = temp.next;
}
return temp;
}
//链表数据反转
public static void reverse(SingleNode head){
//如果链表只有一个节点,直接返回
if(head.next == null || head.next.next == null){
return ;
}
//辅助变量,帮助我们遍历原来的链表temp
SingleNode temp = head.next;
SingleNode next = null;//指向当前节点(temp)的下一个节点
//建立反转表用到的临时节点
SingleNode reverseHeroNode = new SingleNode(0,"");
//遍历原来的链表,反转链表中的数据
while (temp != null){
//保存当前节点的下一个节点,以防止丢失
next = temp.next;
////将后一个节点指向前一个节点
temp.next = reverseHeroNode.next;
//认购将临时节点指向新的节点
reverseHeroNode.next = temp;
temp = next;
}
head.next = reverseHeroNode.next;
}
/**
* 从尾到头打印单链表
* 方式一:反转后遍历,缺点是破坏表结构
* 方式二:利用栈的数据结构特点:先进后出,压入栈后取出就实现了逆序打印
*
* 使用栈的方式实现逆序打印
*/
public static void reversePrint(SingleNode head){
if (head.next == null){
return;//空列表
}
//创建栈,将链表的节点压入栈中
Stack<SingleNode> nodeStack = new Stack<>();
//辅助变量temp协助链表的遍历
SingleNode temp = head.next;
while (temp != null){
nodeStack.push(temp);
temp = temp.next;//后移
}
while (nodeStack.size()>0){
System.out.println(nodeStack.pop());
}
}
//使用递归反向打印单链表
public static void recursionReversePrint(SingleNode head){
if (head.next != null){
recursionReversePrint(head.next);
}
if (head.no != 0){System.out.println(head.no + ":" + head.name);}
}
}
测试
class Test{
public static void main(String[] args) {
//测试代码
//创建节点
SingleNode node1 = new SingleNode(1, "曹操");
SingleNode node2 = new SingleNode(2, "刘备");
SingleNode node3 = new SingleNode(3, "关羽");
SingleNode node4 = new SingleNode(4, "张飞");
//创建一个链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
//加入英雄人物(忽略编号排序)
singleLinkedList.add(node1);
singleLinkedList.add(node2);
singleLinkedList.add(node3);
singleLinkedList.add(node4);
//自行测试(省略)
}
}