单链表

链表

  • 链表是有序列表,线性结构。
  • 链表是以节点的方式来存储,是链式存储。
  • 每个节点包含data域,next域(指向下一个节点)。
  • 节点之间不一定是连续的地址,链表存储空间不连续(不像数组)。
  • 链表分带头节点的链表和没有带头节点的链表,根据实际的需求来确定。

单链表的增删改查

添加节点(创建)
  • 先创建一个hear头节点(头结点不存放数据,仅仅作为当前链表的入口;head 字段的值不能改变,一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了;头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义[当然有些情况下也可存放链表的长度、用做监视哨等等]。),作用就是表示单链表的头。
  • 后面每添加一个节点,就直接加入到链表的最后(首先需要遍历链表,找到链表最后一个节点,当 temp.next == null时,temp 节点指向链表最后一个节点)。
添加【添加时根据编号排序】节点(创建)
  • 首先找到新添加的节点的位置,是通过辅助变量,通过遍历来搞定。
  • 新的节点.next=temp.next。
  • 将temp.next=新的节点。
  • 首先需要遍历链表,找到链表中编号值比 node.no 大的节点,暂且叫它 biggerNode ,然后把 node 插入到 biggerNode 之前即可。
  • 怎么找 biggerNode ?当 temp.next.no > node.no 时,这时temp.next 节点就是 biggerNode 节点。
  • 为什么是 temp.next 节点?只有找到 temp 节点和 temp.next(biggerNode )节点,才能在 temp 节点和 temp.next 节点之间插入 node 节点
遍历链表
  • 通过一个辅助变量遍历,帮助遍历整个链表。
  • 何时遍历完成?temp == null 表明当前节点为 null ,即表示已到链表末尾。
  • 如何遍历?temp = temp.next ,每次输出当前节点信息之后,temp 指针后移。
修改节点
  • 先找到要修改的该节点temp,通过遍历(如何找到指定节点?temp.no = newNode.no)。
  • 找到后,temp.name=newNode.name。
删除节点
  • 找到需要删除的这个节点的前一个节点temp。
  • temp.next=temp.next.next。
  • 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收。
获取倒数第几的节点
  • 首先获取有效节点总长度-指定倒数第几=N. 然后遍历到 N次后返回
单链表的反转(实际节点调换)
  • 定义新链表 DataNode reverseHead = new DataNode();。
  • 遍历原链表, 每遍历一个节点, 将其取出, 并加到 reverseHead的最前端。
单链表的反转(只是反转输出, 实际节点未反转)
  • 通过栈数据结构, 利用栈的先进后出的特点, 实现逆序输出的效果。

代码示例 1 - 添加、遍历

/** 定义链表节点类*/
class DataNode {
    /** 节点编号*/
    public int no;
    /** 节点标题*/
    public String title;
    /** 指向下一个节点, 如果是 null, 则当前节点为尾节点*/
    public DataNode next;

    public DataNode() {}

    public DataNode(int no, String title) {
        this.no = no;
        this.title = title;
    }

    @Override
    public String toString() {
        return "DataNode [no=" + no + ", title=" + title + "]";
    }
}

/** 定义单链表类*/
class SingleLinkedList {
    /** 初始化头节点, 此节点不设定数据*/
    private DataNode head = new DataNode();

    public DataNode getHead() {
        return head;
    }

    /**
     * 添加新节点:
     * 给尾部节点的 next指向新的节点引用
     * */
    public void add(DataNode DataNode) {
        /** 为了从头节点开始往后遍历找出尾部节点*/
        DataNode temp = head;
        /** 遍历找出尾部节点*/
        while(true) {
            /** 当前节点为尾部节点便停止循环*/
            if(temp.next == null) {
                break;
            }
            /** 如果当前节点不为尾部节点, 则再往后移*/
            temp = temp.next;
        }
        /** 将新的节点引用加到, 当前(尾部)节点的 next变量中*/
        temp.next = DataNode;
    }

    /** 遍历所有节点*/
    public void print() {
        if(head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        /** 头节点不能被覆盖, 所有先使用辅助变量转移第一个(有效)节点*/
        DataNode temp = head.next;
        while(true) {
            /** 到了链表尾部时停止遍历*/
            if(temp == null) {
                break;
            }
            /** 输出当前节点的信息*/
            System.out.println(temp);
            /** 往后移一个节点, 继续遍历*/
            temp = temp.next;
        }
    }
}

public class SingleLinkedListApp {
    public static void main(String[] args) {
        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        /** 添加节点*/
        list.add(new DataNode(4, "节点4"));
        list.add(new DataNode(2, "节点2"));
        list.add(new DataNode(1, "节点1"));
        list.add(new DataNode(3, "节点3"));
        /** 输出所有节点*/
        list.print();
    }
}
输出:
> DataNode [no=4, title=节点4]
> DataNode [no=2, title=节点2]
> DataNode [no=1, title=节点1]
> DataNode [no=3, title=节点3]

代码示例 2 - 排序添加(添加时根据编号排序)

/** 排序添加(添加时根据编号排序)*/
    public void addByOrder(DataNode DataNode) {
        DataNode temp = head;
        /** 标志: 是否重复*/
        boolean bRepeat = false;
        while(true) {
            /** 到了链表尾部时停止遍历*/
            if(temp.next == null) {
                break;
            }
            /** 找到了, 排序位*/
            if(temp.next.no > DataNode.no) {
                break;
            } else if (temp.next.no == DataNode.no) {
                /** 编号重复*/
                bRepeat = true;
                break;
            }
            /** 往后移一个节点, 继续遍历*/
            temp = temp.next;
        }
        /** 判断编号是否重复*/
        if(bRepeat) {
            System.out.printf("重复节点 %d, 不能添加\n", DataNode.no);
        } else {
            /** 将新节点插入到下一个(next)位置, 再把之前的 next引用地址指向新节点的下一个(next)*/
            DataNode.next = temp.next;
            temp.next = DataNode;
        }
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        /** 输出所有节点*/
        list.print();

输出:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]

代码示例 3 - 修改

/** 链表修改(修改指定节点信息, 根据编号(no)锁定节点)*/
    public void update(DataNode newDataNode) {
        if(head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        /** 头节点不能被覆盖, 所有先使用辅助变量转移第一个(有效)节点*/
        DataNode temp = head.next;
        /** 标记: 是否找到指定节点*/
        boolean flag = false;
        while(true) {
            /** 到了链表尾部时停止遍历*/
            if (temp == null) {
                break;
            }
            if(temp.no == newDataNode.no) {
                /** 找到节点*/
                flag = true;
                break;
            }
            /** 往后移一个节点, 继续遍历*/
            temp = temp.next;
        }
        if(flag) {
            /** 修改属性*/
            temp.title = newDataNode.title;
        } else {
            System.out.printf("没有找到 编号 %d 的节点\n", newDataNode.no);
        }
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        System.out.println("原链表:");
        list.print();

        DataNode newStu = new DataNode(3,"节点03");
        list.update(newStu);

        System.out.println("修改后的链表:");
        list.print();

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 修改后的链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点03]
> DataNode [no=4, title=节点4]

代码示例 4 - 删除

/** 链表删除*/
    public void del(int no) {
        DataNode 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.printf("要删除的编号 %d 节点不存在", no);
        }
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        System.out.println("原链表:");
        list.print();

        list.del(2);
        System.out.println("删除后的链表:");
        list.print();

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 删除后的链表:
> DataNode [no=1, title=节点1]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]

代码示例 5 - 链表长度(有效节点的个数, 不统计头节点)

 /** 链表长度(有效节点的个数)*/
    public int getLength() {
        /** 空链表*/
        if(head.next == null) {
            return 0;
        }
        int length = 0;
        DataNode temp = head.next;
        while(temp != null) {
            length++;
            /** 往后移一个节点, 继续遍历*/
            temp = temp.next;
        }
        return length;
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        System.out.println("原链表:");
        list.print();

        System.out.println("有效节点个数: "+ list.getLength());

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 有效节点个数: 4

代码示例 6 - 获取倒数第几的节点

/** 获取倒数第几的节点*/
public DataNode findLastIndexNode(int index) {
    if(head.next == null) {
        return null;
    }
    /** 获取有效节点个数*/
    int size = getLength();
    if(index <=0 || index > size) {
        return null;
    }

    DataNode temp = head.next;
    /**
     * 例: size = 4; index = 1
     * size - index = 3
     * */
    int leftSize = size - index;
    for(int i =0; i< leftSize; i++) {
        temp = temp.next;
    }
    return temp;
}

    /** 创建链表实例*/
    SingleLinkedList list = new SingleLinkedList();
    list.addByOrder(new DataNode(4, "节点4"));
    list.addByOrder(new DataNode(2, "节点2"));
    list.addByOrder(new DataNode(1, "节点1"));
    list.addByOrder(new DataNode(3, "节点3"));
    System.out.println("原链表:");
    list.print();

    int lastNo = 2;
    DataNode DataNode = list.findLastIndexNode(lastNo);
    System.out.printf("倒数第 %d 个节点: ", lastNo);
    System.out.println(DataNode);

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 倒数第 2 个节点: DataNode [no=3, title=节点3]

代码示例 7 - 单链表的反转(实际节点调换)

/** 单链表的反转*/
    public void reversetList() {
        /** 必须有1个以上的有效节点*/
        if(head.next == null || head.next.next == null) {
            return ;
        }

        DataNode cur = head.next;
        /** 指向当前节点的下一个节点*/
        DataNode next;
        /** 全新链表*/
        DataNode reverseHead = new DataNode();
        /** 遍历原链表, 每遍历一个节点, 将其取出, 并加到 reverseHead的最前端*/
        while(cur != null) {
            /** 暂时保存当前节点的下一个节点*/
            next = cur.next;
            /** 将 cur的下一个节点指向新的链表的最前端*/
            cur.next = reverseHead.next;
            /** 将 cur连接到新的链表上*/
            reverseHead.next = cur;
            /** 将 cur后移*/
            cur = next;
        }
        /** 将 head.next指向 reverseHead.next*/
        head.next = reverseHead.next;
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        System.out.println("原链表:");
        list.print();

        System.out.println("反转链表:");
        list.reversetList();
        list.print();

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 反转链表:
> DataNode [no=4, title=节点4]
> DataNode [no=3, title=节点3]
> DataNode [no=2, title=节点2]
> DataNode [no=1, title=节点1]

代码示例 8 - 单链表的反转(只是反转输出, 实际节点未反转)

/**
     * 单链表的反转(只是反转输出, 实际节点未反转):
     * 通过栈数据结构, 利用栈的先进后出的特点, 实现逆序输出的效果
     * */
    public void reversePrint() {
        if(head.next == null) {
            return;
        }
        /** 创建一个栈*/
        Stack<DataNode> stack = new Stack<>();
        DataNode cur = head.next;
        while(cur != null) {
            /** 将链表的所有节点压入栈*/
            stack.push(cur);
            /** 将 cur后移*/
            cur = cur.next;
        }
        /** 将栈中的节点一一输出(出栈)*/
        while (stack.size() > 0) {
            System.out.println(stack.pop());
        }
    }

        /** 创建链表实例*/
        SingleLinkedList list = new SingleLinkedList();
        list.addByOrder(new DataNode(4, "节点4"));
        list.addByOrder(new DataNode(2, "节点2"));
        list.addByOrder(new DataNode(1, "节点1"));
        list.addByOrder(new DataNode(3, "节点3"));
        System.out.println("原链表:");
        list.print();

        System.out.println("逆序打印单链表:");
        list.reversePrint();

输出:
> 原链表:
> DataNode [no=1, title=节点1]
> DataNode [no=2, title=节点2]
> DataNode [no=3, title=节点3]
> DataNode [no=4, title=节点4]
> 逆序打印单链表:
> DataNode [no=4, title=节点4]
> DataNode [no=3, title=节点3]
> DataNode [no=2, title=节点2]
> DataNode [no=1, title=节点1]
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值