数据结构系列—线性表实战(链式存储)

在数据结构系列—线性表(二),提到了结点、头指针、头结点、插入方式等概念,今天就围绕这几个概念实现一个添加、删除、查找等功能的单向链表。废话不多说,直接上代码。白话文字比较多,需要好好消化一下。/** * 链式存储: * 1、插入:头插法,尾插法、根据下标插入 * 2、删除:根据下标删除 * 3、根据下标查找 * 4、清空表 * 5、获取表长度 */public class TestLinkedList { private int size; private Node h
摘要由CSDN通过智能技术生成

数据结构系列—线性表(二),提到了结点、头指针、头结点、插入方式等概念,今天就围绕这几个概
念实现一个添加、删除、查找等功能的单向链表。废话不多说,直接上代码。白话文字比较多,需要好好消化一下。

/**
 * 链式存储:
 * 1、插入:头插法,尾插法、根据下标插入
 * 2、删除:根据下标删除
 * 3、根据下标查找
 * 4、清空表
 * 5、获取表长度
 */
public class TestLinkedList {
    private int size;
    private Node header;//头结点
    private Node tail;//尾结点

    public TestLinkedList() {
        size = 0;
        header = null;
        tail = null;
    }

    /**
     * 返回链表的长度
     */
    public int length() {
        return size;
    }

    /**
     * 清空线性表
     */
    public void clear() {
        header = null;
        tail = null;
        size = 0;

    }

    /**
     * 尾插法——插入元素的尾部即可
     * 比如插入 a b c,在链表中的顺序应该是   a b c
     */
    public void addTail(Object element) {
        if (header == null) {//链表为空
            header = new Node(element, null);//创建一个头指针(链表中的第一个结点存储位置叫作"头指针")
            tail = header;
        } else {
            Node newNode = new Node(element, null);//由于插入的是尾部,最后一个结点指针设置为 null 或 ^ 表示。
            tail.next = newNode;//尾结点指针指向新插入的结点
            tail = newNode;//尾结点替换成新插入的结点
        }
        size++;

    }

    /**
     * 头插法——插入的元素都放在头部
     * 比如插入 a b c,在链表中的顺序应该是  c b a
     * 当插入 a结点的时候,如果链表为空,那么直接进行尾结点的赋值。头部也进行了新插入结点a的赋值。
     * 当插入 b结点的时候,这时候头结点为a  尾结点为a,新插入的结点b需要当做头结点,那么它的next指向就是上个头结点a。b->next=结点a,结点a->next=null
     * 当插入 c结点的时候,目前链表的结构是b->next=结点a,结点a->next=null,就是重复上一个操作,将结点c的指针指向结点b,c->next=结点b
     * <p>
     * 最终形成了 c->next=结点b, b->next=结点a,a->next=null,
     */
    public void addHeader(Object element) {
        header = new Node(element, header);//链表为空的话,第一次插入,header肯定为null
        if (tail == null) {
            tail = header;//如果链表尾空,最后一个节点就是当前插入的节点
        }
        size++;
    }

    /**
     * 根据下标删除
     *
     * @param index
     * @return
     */
    public Object delete(int index) {
        if (index < 0 || index > size - 1) {
            throw new IndexOutOfBoundsException("链表下标越界");
        }
        Node del = null;
        if (index == 0) {//处理头结点
            del = header;
            header = header.next;//头结点删除后,将头结点指向的结点进行赋值,(头结点a和结点b,这时候删除头结点a,就需要将a->next=结点b,设置为头结点)
        } else {
            //  还记得前面讲过的,删除中间节点的赋值顺序了吗?s = p->next,p->next = s->next; 结点s需要被删除。只需要将结点s的前一个结点p的指针域指向结点s下面的结点q即可。
            // 1、需要先获取上一个位置的指针域,
            Node prev = getNodeLastIndex(index - 1);
            del = prev.next;//通过当前位置的上一个结点,获取到当前被删除结点。prev.next=被删除的结点
            //2、将del结点的上一个结点的指针,指向del结点下一个结点。完成一个删除
            prev.next = del.next;
            del.next = null;
        }
        size--;
        return del.data;

    }

    /**
     * 根据索引插入
     * 1、获取当前索引前一个结点,
     * 2、将前一个结点指向当前新结点,新节点指向下一个结点
     *
     * @param index
     * @param element
     * @return
     */
    public void insert(int index, Object element) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("链表下标越界");

        }
        if (header == null) {
            addTail(element);//尾插法插入
        } else {
            if (index == 0) {//插入头部的话,需要用到头插法
                addHeader(element);
            } else {
                //还记得前面讲过的 结点s插入对应位置的步骤吗?目前有两个结点p、q,p.next=q,这时候要加一个结点s。步骤如下
                //s->next = p->next,p->next = s; 也就是说,先将新插入"结点s"的指针域指向"结点q",再将"结点p"的指针指向"结点s"即可。
                //步骤不能反过来的原因也分析过了,具体看:下面放入的链接
                Node prev = getNodeLastIndex(index - 1);//获取当前索引前一个结点
                prev.next = new Node(element, prev.next);//拆解开来就是下面两个步骤

//                1、将新结点 element 的指针指向当前结点,也就是 prev.next=当前结点
//                Node newNode = new Node(element);
//                newNode.next = prev.next;
//                2、将当前位置的上个结点的指针,指向当前newNode结点即可。
//                prev.next = newNode;
                size++;

            }

        }

    }

    /**
     * 获取上一个结点
     *
     * @param index
     * @return
     */
    private Node getNodeLastIndex(int index) {
        if (index < 0 || index > size - 1) {
            throw new IndexOutOfBoundsException("链表下标越界");
        }
        Node current = header;//从头结点开始,一次往下查找
        for (int i = 0; i < size && current != null; i++) {
            if (i == index) {
                return current;
            }
            //比如当前有三个结点  a b c ,查找的时候会先查找结点a的指向 然后查找结点b的指向 ,最后查找结点c的指向  。如果位置对应则顺利获取上一个结点
            current = current.next;
        }
        return null;
    }

    /**
     * 根据下标获取链表元素
     *
     * @param index
     * @return
     */
    public Object get(int index) {

        return getNodeLastIndex(index).data;

    }

    //链表的每个节点类
    private class Node {
        private Object data;//数据域
        private Node next;//指针域 指向下一个节点的引用

        public Node(Object data) {
            this.data = data;
        }

        public Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }
    }
}

代码中 insert() 方法里提到的链接,数据结构系列—线性表(二)

测试部分
 TestLinkedList testLinkedList = new TestLinkedList();
        //尾插法
        testLinkedList.addTail(1);
        testLinkedList.addTail(2);
        testLinkedList.addTail(3);
        testLinkedList.addTail(4);
        //查找链表中每个index对应的值
        searchData(stringBuilder, testLinkedList);


        //根据对应位置插入元素
        testLinkedList.insert(0, 0);
        //插入之后再次查询
        searchData(stringBuilder, testLinkedList);


        //使用头插法 列表前4位的顺序应该是 4 3 2 1
        testLinkedList.addHeader(1);
        testLinkedList.addHeader(2);
        testLinkedList.addHeader(3);
        testLinkedList.addHeader(4);
        //头插法之后再次查询
        searchData(stringBuilder, testLinkedList);


        //删除第4个位置的元素 也就是把0删除
        testLinkedList.delete(4);
        searchData(stringBuilder, testLinkedList);
        Log.d("testLinkedList", stringBuilder.toString());
        System.out.println(stringBuilder.toString());
    }

    private void searchData(StringBuilder stringBuilder, TestLinkedList testLinkedList) {
        //获取长度
        int length = testLinkedList.length();
        stringBuilder.append("\n")
                .append("\nlength--")
                .append(length);
        for (int i = 0; i < testLinkedList.length(); i++) {
            int data = (int) testLinkedList.get(i);
            stringBuilder.append("\n")
                    .append("\ndata--")
                    .append(data);
        }
    }
打印:
	testLinkedList: length--4   
    data--1    
    data--2    
    data--3    
    data--4
    
    length--5    
    data--0    
    data--1    
    data--2    
    data--3    
    data--4   
     
    length--9    
    data--4    
    data--3    
    data--2    
    data--1    
    data--0    
    data--1    
    data--2    
    data--3    
    data--4   
     
    length--8    
    data--4    
    data--3    
    data--2    
    data--1    
    data--1   
    data--2    
    data--3    
    data--4
总结:

本次链表与数组的实战就告一段落了,通过这次实战能够掌握到顺序存储与链式存储的物理区别。一种靠指针移动,一种靠开辟空间,二者的优势劣势前面讲过,这里就不再过多叙述了。

接下来会学习逻辑结构中的:栈、队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值