常见数据结构

181 阅读8分钟

常见的数据结构主要包括以下几种:

一、数组(Array)

  1. 定义

    • 数组是一种线性的数据结构,它是由相同类型的元素(如整数、浮点数、字符等)组成的有序集合。这些元素在内存中是连续存储的,通过索引来访问其中的元素。索引从 0 开始,例如,对于一个包含 n 个元素的数组,其索引范围是 0 到 n - 1。
  2. 示例

    • 例如,定义一个整数数组int[] numbers = {1, 2, 3, 4, 5};,可以通过numbers[0]访问第一个元素 1,numbers[2]访问第三个元素 3 等。
  3. 优点

    • 访问元素速度快。因为数组的元素在内存中是连续存储的,通过计算偏移量可以快速定位到任何一个元素。例如,在一个长度为 n 的数组中,访问第 i 个元素的时间复杂度是 O (1)。
    • 简单直观,易于理解和使用。
  4. 缺点

    • 插入和删除元素效率低。在数组中间插入或删除一个元素,需要移动后面的元素,时间复杂度为 O (n)。例如,在一个已经排好序的数组中间插入一个新元素,需要将插入位置后面的所有元素向后移动一位。

    • 大小固定。在创建数组时,需要指定数组的大小,一旦创建,其大小通常很难改变。如果要扩展数组,可能需要创建一个新的更大的数组,并将原数组的元素复制到新数组中。

二、链表(Linked List)

  1. 定义

    • 链表是一种线性的数据结构,由一系列节点组成。每个节点包含数据部分和指向下一个节点的指针(在单链表中)。如果是双向链表,节点还包含指向前一个节点的指针。链表的节点在内存中不是连续存储的,而是通过指针链接在一起。
  2. 示例

    • 单链表节点的结构可以用如下代码表示(以 C++ 为例):
   struct ListNode {
       int val;
       ListNode* next;
       ListNode(int x) : val(x), next(NULL) {}
   };
  • 可以通过head指针来访问链表的头节点,然后沿着next指针遍历整个链表。

  1. 优点

    • 插入和删除操作高效。在链表中插入或删除一个节点,只需要修改相关节点的指针,时间复杂度为 O (1)(如果已经知道要插入或删除的位置)。例如,在链表中间插入一个新节点,只需要将新节点插入到合适的位置,更新前后节点的指针即可。
    • 可以动态地分配内存。链表的长度可以根据需要动态地增加或减少,不需要预先指定大小。
  2. 缺点

    • 访问元素效率低。因为链表的节点在内存中不是连续存储的,要访问链表中的某个元素,需要从链表头开始遍历,时间复杂度为 O (n)。例如,要访问链表中的第 n 个节点,可能需要遍历 n 个节点才能找到。

三、栈(Stack)

  1. 定义

    • 栈是一种特殊的线性数据结构,它遵循后进先出(LIFO - Last In First Out)的原则。可以把栈想象成一个只有一个开口的容器,元素只能从这个开口进出。进入栈的操作称为 “入栈”(push),从栈中取出元素的操作称为 “出栈”(pop)。
  2. 示例

    • 以整数栈为例,在程序中可以用数组或链表来实现栈。如果用数组实现一个简单的栈(以 Python 为例):

   class Stack:
       def __init__(self):
           self.stack = []
       def push(self, item):
           self.stack.append(item)
       def pop(self):
           if not self.is_empty():
               return self.stack.pop()
       def is_empty(self):
           return len(self.stack) == 0
  • 可以使用push操作将元素压入栈,pop操作将栈顶元素弹出。

  1. 优点

    • 简单易用,对于实现一些具有后进先出特性的功能非常方便。例如,函数调用栈,当一个函数调用另一个函数时,当前函数的状态信息(如局部变量、返回地址等)被压入栈中,当被调用函数返回时,这些信息从栈中弹出,恢复之前函数的执行。
    • 栈的操作(入栈和出栈)时间复杂度为 O (1),因为只涉及到对栈顶元素的操作。
  2. 缺点

    • 栈的容量有限(如果是用数组实现的栈)。如果栈满了,就无法再进行入栈操作,除非扩展栈的大小。

    • 只能访问栈顶元素。要访问栈中的其他元素,需要先将栈顶元素弹出,这可能会改变栈的状态。

四、队列(Queue)

  1. 定义

    • 队列是一种线性数据结构,遵循先进先出(FIFO - First In First Out)的原则。可以把队列想象成一个排队的队伍,元素从队尾进入队列(入队操作,enqueue),从队头离开队列(出队操作,dequeue)。
  2. 示例

    • 以整数队列为例,同样可以用数组或链表来实现队列。用数组实现一个简单的队列(以 Java 为例):
   class Queue {
       private int[] queue;
       private int front;
       private int rear;
       private int size;
       public Queue(int capacity) {
           queue = new int[capacity];
           front = 0;
           rear = -1;
           size = 0;
       }
       public void enqueue(int item) {
           if (!isFull()) {
               rear = (rear + 1) % queue.length;
               queue[rear] = item;
               size++;
           }
       }
       public int dequeue() {
           if (!isEmpty()) {
               int item = queue[front];
               front = (front + 1) % queue.length;
               size--;
               return item;
           }
           return -1;
       }
       public boolean isEmpty() {
           return size == 0;
       }
       public boolean isFull() {
           return size == queue.length;
       }
   }
  • 这里通过enqueue操作将元素入队,dequeue操作将队头元素出队。

  1. 优点

    • 对于处理按顺序到达的数据非常有效。例如,在操作系统中,进程调度队列就是按照先进先出的原则,先到达的进程先获得 CPU 资源。
    • 入队和出队操作时间复杂度在理想情况下(如用链表实现)为 O (1)。
  2. 缺点

    • 同样存在容量问题(如果是用数组实现的队列)。当队列满时,无法再进行入队操作,除非扩展队列大小。

    • 查找特定元素效率低。要在队列中查找一个特定的元素,需要遍历队列中的部分或全部元素,时间复杂度为 O (n)。

五、树(Tree)

  1. 定义

    • 树是一种非线性的数据结构,它是由 n(n >= 0)个节点组成的有限集合。当 n = 0 时,称为空树。在非空树中,有一个特殊的节点称为根节点(root),其余节点可以分为 m(m >= 0)个互不相交的有限集合,每个集合本身又是一棵树,称为根节点的子树。
  2. 示例

    • 以二叉树为例,二叉树是一种特殊的树,每个节点最多有两个子节点,分别称为左子节点和右子节点。下面是一个简单二叉树节点的定义(以 Python 为例):
   class TreeNode:
       def __init__(self, val=0, left=None, right=None):
           self.val = val
           self.left = left
           self.right = right
  • 可以通过根节点来访问整个二叉树,例如,通过递归的方式遍历二叉树的节点。

  1. 优点

    • 树结构可以很好地表示层次关系。例如,文件系统的目录结构就是一个树形结构,根目录是树的根节点,子目录是子节点,文件可以看作是叶子节点。
    • 对于一些搜索、排序等操作,树结构可以提供高效的算法。例如,二叉搜索树(BST)可以在平均时间复杂度为 O (log n) 的情况下进行插入、删除和查找操作。
  2. 缺点

    • 实现和理解相对复杂。树的操作(如插入、删除、遍历等)通常需要使用递归等复杂的算法,对于初学者来说可能比较难掌握。

    • 树的平衡性问题。在一些树结构(如二叉搜索树)中,如果树不平衡,操作的时间复杂度可能会退化为 O (n)。例如,当二叉搜索树退化成一条链表时,查找操作的效率会变得很低。

六、图(Graph)

  1. 定义

    • 图是一种更为复杂的非线性数据结构,由顶点(vertex)和边(edge)组成。顶点表示图中的节点,边表示顶点之间的关系。图可以分为有向图(边有方向)和无向图(边没有方向)。

  2. 优点

    • 图可以很好地表示各种复杂的关系。例如,社交网络可以用图来表示,其中人是顶点,人与人之间的朋友关系是边。
    • 图算法可以解决很多实际问题,如最短路径问题(如 Dijkstra 算法)、最小生成树问题(如 Prim 算法和 Kruskal 算法)等。
  3. 缺点

    • 图的存储和操作比较复杂。无论是邻接矩阵还是邻接表表示法,在处理大规模图时,都需要占用大量的内存和计算资源。
    • 图算法的时间复杂度通常较高。例如,在一个有 n 个顶点的图中,一些算法的时间复杂度可能是 O (n^2) 甚至更高。

七、散列表(哈希表)

根据关键码值(Key)而直接进行访问的数据结构。它通过一个散列函数(Hash Function)将关键码映射到一个特定的位置(槽位,Slot)来存储数据。