数据结构与算法——部分常见的数据结构类型解析与算法实现

数据结构大致包含以下几种存储结构:

  • 线性表,还可细分为顺序表、链表、栈和队列等;
  • 结构,包括普通树,二叉树,线索二叉树等;
  • 存储结构;

下面对部分典型的数据结构做详细讲解

一、数组

数组是相同数据类型的元素按一定顺序排列的集合,是一块连续的内存空间。数组的优点是:get和set操作时间上都是O(1)的;缺点是:add和remove操作时间上都是O(N)的。

Java中,Array就是数组,此外,ArrayList使用了数组Array作为其实现基础,它和一般的Array相比,最大的好处是,我们在添加元素时不必考虑越界,元素超出数组容量时,它会自动扩张保证容量。

Vector和ArrayList相比,主要差别就在于多了一个线程安全性,但是效率比较低下。如今java.util.concurrent包提供了许多线程安全的集合类(比如 LinkedBlockingQueue),所以不必再使用Vector了。

1 int[] ints = new int[10];
2 ints[0] = 5;//set
3 int a = ints[2];//get
4 int len = ints.length;//数组长度

二、链表

链表是一种非连续、非顺序的结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,链表由一系列结点组成。链表的优点是:add和remove操作时间上都是O(1)的;缺点是:get和set操作时间上都是O(N)的,而且需要额外的空间存储指向其他数据地址的项。

查找操作对于未排序的数组和链表时间上都是O(N)。

Java中,LinkedList 使用链表作为其基础实现。

1 LinkedList<String> linkedList = new LinkedList<>();
2 linkedList.add("addd");//add
3 linkedList.set(0,"s");//set,必须先保证 linkedList中已经有第0个元素
4 String s =  linkedList.get(0);//get
5 linkedList.contains("s");//查找
6 linkedList.remove("s");//删除
7 
8 //以上方法也适用于ArrayList

三、队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,亦即所谓的先进先出(FIFO)。

Java中,LinkedList实现了Deque,可以做为双向队列(自然也可以用作单向队列)。另外PriorityQueue实现了带优先级的队列,亦即队列的每一个元素都有优先级,且元素按照优先级排序。

 1 Deque<Integer> integerDeque = new LinkedList<>();
 2 // 尾部入队,区别在于如果失败了
 3 // add方法会抛出一个IllegalStateException异常,而offer方法返回false
 4 integerDeque.offer(122);
 5 integerDeque.add(122);
 6 // 头部出队,区别在于如果失败了
 7 // remove方法抛出一个NoSuchElementException异常,而poll方法返回false
 8 int head = integerDeque.poll();//返回第一个元素,并在队列中删除
 9 head = integerDeque.remove();//返回第一个元素,并在队列中删除
10 // 头部出队,区别在于如果失败了
11 // element方法抛出一个NoSuchElementException异常,而peek方法返回null。
12 head = integerDeque.peek();//返回第一个元素,不删除
13 head = integerDeque.element();//返回第一个元素,不删除

四、栈

栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。它体现了后进先出(LIFO)
的特点。

Java中,Stack实现了这种特性,但是Stack也继承了Vector,所以具有线程安全线和效率低下两个特性,最新的JDK8中,推荐用Deque来实现栈,比如:

1 栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。它体现了后进先出(LIFO)
2 的特点。
3 
4 Java中,Stack实现了这种特性,但是Stack也继承了Vector,所以具有线程安全线和效率低下两个特性,最新的JDK8中,推荐用Deque来实现栈,比如:

五、集合

集合是指具有某种特定性质的具体的或抽象的对象汇总成的集体,这些对象称为该集合的元素,其主要特性是元素不可重复。

在Java中,HashSet 体现了这种数据结构,而HashSet是在MashMap的基础上构建的。LinkedHashSet继承了HashSet,使用HashCode确定在集合中的位置,使用链表的方式确定位置,所以有顺序。TreeSet实现了SortedSet 接口,是排好序的集合(在TreeMap 基础之上构建),因此查找操作比普通的Hashset要快(log(N));插入操作要慢(log(N)),因为要维护有序。

1 HashSet<Integer> integerHashSet = new HashSet<>();
2 integerHashSet.add(12121);//添加
3 integerHashSet.contains(121);//是否包含
4 integerHashSet.size();//集合大小
5 integerHashSet.isEmpty();//是否为空

六、树

树(tree)是包含n(n>0)个节点的有穷集合,其中:

  • 每个元素称为节点(node);
  • 有一个特定的节点被称为根节点或树根(root)。
  • 除根节点之外的其余数据元素被分为m(m≥0)个互不相交的结合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。

树这种数据结构在计算机世界中有广泛的应用,比如操作系统中用到了红黑树,数据库用到了B+树,编译器中的语法树,内存管理用到了堆(本质上也是树),信息论中的哈夫曼编码等等等等,在Java中TreeSet和TreeMap用到了树来排序(二分查找提高检索速度),不过一般都需要程序员自己去定义一个树的类,并实现相关性质,而没有现成的API。下面就用Java来实现各种常见的树。

七、二叉树

二叉树是一种基础而且重要的数据结构,其每个结点至多只有二棵子树,二叉树有左右子树之分,第i层至多有2^(i-1)个结点(i从1开始);深度为k的二叉树至多有2^(k)-1)个结点,对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。

二叉树的性质:

  1) 在非空二叉树中,第i层的结点总数不超过2^(i-1), i>=1;

  2) 深度为h的二叉树最多有2^h-1个结点(h>=1),最少有h个结点;

  3) 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;

  4) 具有n个结点的完全二叉树的深度为log2(n+1);

  5)有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
    若I为结点编号则 如果I>1,则其父结点的编号为I/2;
    如果2I<=N,则其左儿子(即左子树的根结点)的编号为2I;若2I>N,则无左儿子;
    如果2I+1<=N,则其右儿子的结点编号为2I+1;若2I+1>N,则无右儿子。
    
  6)给定N个节点,能构成h(N)种不同的二叉树,其中h(N)为卡特兰数的第N项,h(n)=C(2*n, n)/(n+1)。

  7)设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i。

满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点;

完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树;

满二叉树是完全二叉树的一个特例。

二叉查找树,又称为是二叉排序树(Binary Sort Tree)或二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
  1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2) 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
  3) 左、右子树也分别为二叉排序树;
  4) 没有键值相等的节点。
  二叉查找树的性质:对二叉查找树进行中序遍历,即可得到有序的数列。
  二叉查找树的时间复杂度:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡二叉树设计的初衷。

二叉树表示:

 1 public class BST<Key extends Comparable<Key>, Value> {
 2     private Node root;             // 根节点
 3 
 4     private class Node {
 5         private Key key;           // 排序的间
 6         private Value val;         // 相应的值
 7         private Node left, right;  // 左子树,右子树
 8         private int size;          // 以该节点为根的树包含节点数量
 9 
10         public Node(Key key, Value val, int size) {
11             this.key = key;
12             this.val = val;
13             this.size = size;
14         }
15     }
16     public BST() {}
17     
18     public int size() {//获得该二叉树节点数量
19         return size(root);
20     }
21     
22     private int size(Node x) {获得以该节点为根的树包含节点数量
23         if (x == null) return 0;
24         else return x.size;
25     }
26 }

查找:

 1 public Value get(Key key) {
 2     return get(root, key);
 3 }
 4 
 5 private Value get(Node x, Key key) {//在以x节点为根的树中查找key
 6     if (x == null) return null;
 7     int cmp = key.compareTo(x.key);
 8     if      (cmp < 0) return get(x.left, key);//递归左子树查找
 9     else if (cmp > 0) return get(x.right, key);//递归右子树查找
10     else              return x.val;//找到了
11 }

插入

 1 public void put(Key key, Value val) {
 2     root = put(root, key, val);
 3 }
 4 
 5 private Node put(Node x, Key key, Value val) {在以x节点为根的树中查找key,val
 6     if (x == null) return new Node(key, val, 1);
 7     int cmp = key.compareTo(x.key);
 8     if      (cmp < 0) x.left  = put(x.left,  key, val);//递归左子树插入
 9     else if (cmp > 0) x.right = put(x.right, key, val);//递归右子树插入
10     else              x.val   = val;
11     x.size = 1 + size(x.left) + size(x.right);
12     return x;
13 }

删除

 1 public Key min() {
 2     return min(root).key;
 3 } 
 4 private Node min(Node x) { 
 5     if (x.left == null) return x; 
 6     else                return min(x.left); 
 7 } 
 8 
 9 public void deleteMin() {
10     root = deleteMin(root);
11 }
12 private Node deleteMin(Node x) {//删除以x为根节点的子树最小值
13     if (x.left == null) return x.right;
14     x.left = deleteMin(x.left);
15     x.size = size(x.left) + size(x.right) + 1;
16     return x;
17 }
18 
19 public void delete(Key key) {
20      root = delete(root, key);
21 }
22 private Node delete(Node x, Key key) {
23     if (x == null) return null;
24 
25     int cmp = key.compareTo(x.key);
26     if      (cmp < 0) x.left  = delete(x.left,  key);//递归删除左子树
27     else if (cmp > 0) x.right = delete(x.right, key);//递归删除右子树
28     else { //该节点就是所要删除的节点
29         if (x.right == null) return x.left;//没有右子树,把左子树挂在原节点父节点上
30         if (x.left  == null) return x.right;//没有左子树,,把右子树挂在原节点父节点上
31         Node t = x;//用右子树中最小的节点来替代被删除的节点,仍然保证树的有序性
32         x = min(t.right);
33         x.right = deleteMin(t.right);
34         x.left = t.left;
35     } 
36     x.size = size(x.left) + size(x.right) + 1;
37     return x;
38 } 

八、图

图是一种较线性表和树更为复杂的数据结构,在线性表中,数据元素之间仅有线性关系,在树形结构中,数据元素之间有着明显的层次关系,而在图形结构中,节点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。图的应用相当广泛,特别是近年来的迅速发展,已经渗入到诸如语言学、逻辑学、物理、化学、电讯工程、计算机科学以及数学的其他分支中。

来源:MageekChiu

链接:https://segmentfault.com/a/1190000009797159

posted on 2021-03-16 11:30  林岑  阅读(254)  评论(0)    收藏  举报

导航