数据结构与算法——部分常见的数据结构类型解析与算法实现
数据结构大致包含以下几种存储结构:
- 线性表,还可细分为顺序表、链表、栈和队列等;
- 树结构,包括普通树,二叉树,线索二叉树等;
- 图存储结构;
下面对部分典型的数据结构做详细讲解
一、数组
数组是相同数据类型的元素按一定顺序排列的集合,是一块连续的内存空间。数组的优点是: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
浙公网安备 33010602011771号