数据结构 —— 数组和链表|8月更文挑战

159 阅读4分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

1 数组(Array)

数据结构-array.png

优点

  • 构建非常简单
  • 能在 O(1) 的时间里根据数组的下标(index)查询某个元素

缺点

  • 构建时必须分配一段连续的空间
  • 查询某个元素是否存在时需要遍历整个数组,耗费 O(n) 的时间(其中,n 是元素的个数)
  • 删除和添加某个元素时,同样需要耗费 O(n) 的时间

基本操作

  • insert:在某个索引处插入元素
  • get:读取某个索引处的元素
  • delete:删除某个索引处的元素
  • size:获取数组的长度

案例一:翻转字符串“algorithm”

翻转字符串algorithm.gif

解法:用两个指针,一个指向字符串的第一个字符 a,一个指向它的最后一个字符 m,然后互相交换。交换之后,两个指针向中央一步步地靠拢并相互交换字符,直到两个指针相遇。这是一种比较快速和直观的方法。

案例二:给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。

说明:你可以假设字符串只包含小写字母。

解题思路:字母异位词,也就是两个字符串中的相同字符的数量要对应相等。

  • 可以利用两个长度都为 26 的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等
  • 可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0

2 链表(Linked List)

链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。

链表(LinkedList).png

优点

  • 链表能灵活地分配内存空间
  • 能在 O(1) 时间内删除或者添加元素

缺点

  • 不像数组能通过下标迅速读取元素,每次都要从链表头开始一个一个读取
  • 查询第 k 个元素需要 O(k) 时间

基本操作

  • insertAtEnd:在链表结尾插入元素
  • insertAtHead:在链表开头插入元素
  • delete :删除链表的指定元素
  • deleteAtHead :删除链表第一个元素
  • search:在链表中查询指定元素
  • isEmpty:查询链表是否为空

应用场景

  • 如果要解决的问题里面需要很多快速查询,链表可能并不适合
  • 如果遇到的问题中,数据的元素个数不确定,而且需要经常进行数据的添加和删除,那么链表会比较合适
  • 如果数据元素大小确定,删除和插入的操作并不多,那么数组可能更适合

链表实现数据结构

① 用链表实现队列

用链表实现栈.png

② 用链表实现栈

用链表实现队列.png

链表翻转算法

① 递归翻转

 /**
  * 链表递归翻转模板
  */
 public Node reverseLinkedList(参数0) {
     // Step1:终止条件
     if (终止条件) {
         return;
     }
     
     // Step2:逻辑处理:可能有,也可能没有,具体问题具体分析
     // Step3:递归调用
     Node reverse = reverseLinkedList(参数1);
     // Step4:逻辑处理:可能有,也可能没有,具体问题具体分析
 }
 ​
 /**
  * 链表递归翻转算法
  */
 public Node reverseLinkedList(Node head) {
         // Step1:终止条件
         if (head == null || head.next == null) {
             return head;
         }
     
         // Step2:保存当前节点的下一个结点
         Node next = head.next;
         // Step3:从当前节点的下一个结点开始递归调用
         Node reverse = reverseLinkedList(next);
         // Step4:head挂到next节点的后面就完成了链表的反转
         next.next = head;
         // 这里head相当于变成了尾结点,尾结点都是为空的,否则会构成环
         head.next = null;
         return reverse;
 }

② 三指针翻转

 public static Node reverseLinkedList(Node head) {
     // 单链表为空或只有一个节点,直接返回原单链表
     if (head == null || head.getNext() == null) {
         return head;
     }
     
     // 前一个节点指针
     Node preNode = null;
     // 当前节点指针
     Node curNode = head;
     // 下一个节点指针
     Node nextNode = null;
     while (curNode != null) {
         // nextNode 指向下一个节点
         nextNode = curNode.getNext();
         // 将当前节点next域指向前一个节点
         curNode.setNext(preNode);
         // preNode 指针向后移动
         preNode = curNode;
         // curNode指针向后移动
         curNode = nextNode;
     }
     
     return preNode;
 }

③ 利用栈翻转

 public Node reverseLinkedList(Node node) {
     Stack<Node> nodeStack = new Stack<>();
     // 存入栈中,模拟递归开始的栈状态
     while (node != null) {
         nodeStack.push(node);
         node = node.getNode();
     }
     
     // 特殊处理第一个栈顶元素:反转前的最后一个元素,因为它位于最后,不需要反转
     Node head = null;
     if ((!nodeStack.isEmpty())) {
         head = nodeStack.pop();
     }
     
     // 排除以后就可以快乐的循环
     while (!nodeStack.isEmpty()) {
         Node tempNode = nodeStack.pop();
         tempNode.getNode().setNode(tempNode);
         tempNode.setNode(null);
     }
     
     return head;
 }

2.1 单向链表

单向链表.png

单向链表包含两个域:

  • 一个数据域:用于存储数据
  • 一个指针域:用于指向下一个节点(最后一个节点则指向一个空值):

单链表的遍历方向单一,只能从链头一直遍历到链尾。它的缺点是当要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低,而双向链表的出现恰好解决了这个问题。单向链表代码如下:

 public class Node<E> {
    private Eitem;
    private Node<E> next;
 }

2.2 双向链表

双向链表.png

双向链表的每个节点由三部分组成:

  • prev指针:指向前置节点
  • item节点:数据信息
  • next指针:指向后置节点

双向链表代码如下:

 public class Node<E> {
    private E item;
    private Node<E> next;
    private Node<E> prev;
 }

2.3 循环链表

循环链表又分为单循环链表和双循环链表,也就是将单向链表或双向链表的首尾节点进行连接。

  • 单循环链表

单循环链表.png

  • 双循环链表

双循环链表.png