数据结构(三) --- 单链表

线性表(二) — 单链表


单链表的定义


链式存储结构的特点: 用一组任意的存储单元来存储线性表的数据元素(这组存储单元可以地址连续,也可以地址不连续);为了让地址不连续的元素也能在逻辑关系上保持相连,那么,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置);

节点(node): 由元素数据本身和指向后继的信息组成;其中,存储元素数据本身的域我们称之为数据域(data);存储指向后继信息的域我们称之为指针域(next);指针域通常称为指针或链;

单链表: n个节点链结成一个链表,即为线性表的链式存储结构;又由于此链表中的每个节点只有一个指针域,所以称之为线性链表或单链表;

注意:

  1. 整个链表的存取必须从头指针开始,头指针指向单链表的第一个节点(首元节点);
  2. 由于最后一个数据元素没有直接后继,所以最后一个节点的指针为null;
  3. 用单链表表示线性表时,每个数据元素之间的逻辑关系是由节点中的指针来指示的;
  4. 在使用单链表时,只需要关注数据元素之间的逻辑关系,不需要去关注物理存储地址;

单链表的实现


单链表的初始化:

  • 目的:创建并初始化头结点,并让其指针域为空;

    /**
    * 单链表的头节点
    */
    private Node<E> first;
    
    /**
    * 临时节点
    */
    private Node<E> temp;
    
    /**
    * 存储单链表中的元素个数
    */
    private int size = 0;
    
    /**
    * 初始化头结点,让头节点的指针域为空
    */
    public LinkList() {
        first = new Node<>();
        first.next = null;
    }
    

添加元素:

  • 在单链表末尾加入元素:

    /**
    * 向单链表的末尾加入元素
    * @param element   要加入的元素
    */
    public void add(E element) {
        // 创建新节点
        Node<E> node = new Node<>(element, null);
        temp = first;
        if (temp != null) {
            // 让temp遍历至末尾元素
            for (int i = 0; i < size; i++) {
                temp = temp.next;
            }
        }
        temp.next = node; // 让末尾元素的节点指向新加入的节点
        size ++;
    }
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SEKFzH8F-1576287023254)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20191208201359292.png)]

  • 在指定索引位置加入元素:

    /**
    * 在指定的位置插入给定的元素
    * @param index     要插入的位置
    * @param element   要插入的元素
    */
    public void insert(int index, E element) {
        // 检查索引
        checkIndex(index);
        // 创建新的节点
        Node<E> node = new Node<>(element, null);
        temp = first; // 临时节点
        if (temp != null) {
            // 遍历到要插入的位置
            for (int i = 0; i < index; i++) {
                temp = temp.next;
            }
            node.next = temp.next;  // 新元素的节点指向后一个节点
            temp.next = node;  // 前一个节点指向新节点
            size ++;
        }
    }
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4icO4Bb-1576287023258)(G:\学习笔记\数据结构\图片\单链表2.png)]

  • 合并链表:
/**
* 将两个单链表连接起来
* @param list1
* @param list2
* @return
*/
public LinkList<E> connect(LinkList<E> list1,
                           LinkList<E> list2) {
    // 获取list1的最后一个节点
    Node end = getNode(list1.size - 1);
    // 让list1的最后一个节点指向list2的首节点
    end.next = list2.getNode(0);
    // list1 size增加
    list1.size += list2.size;
    return list1;
}

取值操作:

  • 根据索引取元素:

    /**
    * 获取指定索引位置的元素
    * @param index 需要获取的索引
    * @return  返回查到的元素值
     */
    public E getElement(int index) {
        checkIndex(index);
        temp = getNode(index);  // 把相同步骤封装了,后面会给到代码
        return temp.data;
    }
    

  • 根据元素返回索引:

    /**
    * 根据给定的元素返回对应的索引
    * @param element   需要查找的元素
    * @return  返回索引值,查找失败返回-1
    */
    public int getIndex(E element) {
        temp = first;
        if (temp != null) {
            // 遍历单链表并与每个元素进行比较
            for (int i = 0; i < size; i++) {
                temp = temp.next;
                if (temp.data.equals(element)) {
                    return i;
                }
            }
        }
        return -1;
    }
    

修改元素:

/**
* 将指定索引位置的元素替换成给定的元素
* @param index     指定的索引位置
* @param element   需要替换成的元素
*/
public void set(int index, E element) {
    checkIndex(index);
    temp = getNode(index);
    temp.data = element;
}

删除元素:

  • 根据索引删除元素:

    /**
    * 删除指定索引位置的元素
    * @param index 删除元素的索引位置
    */
    public void delete(int index) {
        checkIndex(index);
        if (index == 0) {
            temp = first;
            temp.next = temp.next.next;
        } else {
            getNode(index - 1);
            temp.next = temp.next.next;
        }
        size --;
    }
    

  • 根据元素值删除:

    /**
    * 删除给定的元素
    * @param element 需要删除的元素
    */
    public void remove(E element) {
        int index = getIndex(element); // 根据元素值找到其对应的索引值
        delete(index);	// 调用根据索引删除元素
    }
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOt4mrGP-1576287023259)(G:\学习笔记\数据结构\图片\单链表3.png)]

辅助方法:

/**
* 检查索引是否越界
* @param index 需要检查的索引
*/
public void checkIndex(int index) {
    if(index < 0 || index >= size) {
        throw new RuntimeException("索引不合法:" + index);
    }
}

/**
* 将遍历链表封装起来
* @param index
*/
private Node getNode(int index) {
    temp = first;
    if (temp != null) {
        temp = temp.next;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
    }
    return temp;
}

@Override
public String toString() {
    StringBuilder sb = new StringBuilder("[");
    Node temp = first;
    if (size == 0) {
        return "[]";
    } else {
        temp = temp.next;
        for (int i = 0; i < size; i++) {
            sb.append(temp.data).append(",");
            temp = temp.next;
        }
    }
    sb.setCharAt(sb.length() - 1, ']');
    return sb.toString();
}

Node节点:

/**
* 单链表中的节点
*/
private static class Node<E> {
    E data;
    Node next;

    public Node(){}

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

单链表的缺点


缺点: 所有操作的时间复杂度都为O(n)

原因: 因为单链表没有索引,要找到指定位置的元素时只能从头开始遍历,直到到达该索引位置,才能获取对应的元素;


循环链表


定义: 循环链表(Circular Linked List)是另一种形式的链式存储结构(其实它是一种特殊的单链表);

特点: 表中的最后一个节点的指针域指向头节点,整个链表形成一个环;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvtwoYpB-1576287023260)(G:\学习笔记\数据结构\图片\循环链表1.png)]

循环链表的实现


  循环链表的实现与单链表大致相同,只是在末尾加入的方法有所不同,因为遍历到尾节点时结束的条件不再是temp.next = null,而是temp.next.equals(first)


尾部加入操作:

/**
* 向单链表的末尾加入元素
* @param element   要加入的元素
*/
public void add(E element) {
    // 创建新节点
    Node<E> node = new Node<>(element, null);
    temp = first;
    // 判断是否是第一次加入
    if (size == 0) {
        temp.next = node;   // 头节点指向新加入元素
    } else {
        // 如果不是第一次加入就遍历找到尾节点,尾节点指向头指针;
        while (!temp.next.equals(first)) {
            temp = temp.next;
        }
        temp.next = node; // 尾节点指向新的节点
    }
    node.next = first;  // 新节点指向头结点
    size ++;
}
发布了22 篇原创文章 · 获赞 0 · 访问量 354

猜你喜欢

转载自blog.csdn.net/Yi__Ying/article/details/103536101