如何彻底搞懂 Java 数据结构?|CSDN 博文精选
作者 | 张振华.Jack责编| 郭芮出品 |CSDN 博客本文和大家一起来重温《Java数据结构》经典之作。Java数据结构要理解Java数据结构,必须能清楚何为数据结构?数据结构:Data_Structure,它是储存数据的一种结构体,在此结构中储存一些数据,而这些数据之间有一定的关系。而各数据元素之间的相互关系,...
·
作者 | 张振华.Jack
责编 | 郭芮
出品 | CSDN 博客
![](https://i-blog.csdnimg.cn/blog_migrate/fe1bebfcc6b5ca8a354ca8fea2c30183.png)
Java数据结构
数据结构:
Data_Structure,它是储存数据的一种结构体,在此结构中储存一些数据,而这些数据之间有一定的关系。 而各数据元素之间的相互关系,又包括三个组成成分,数据的逻辑结构,数据的存储结构和数据运算结构。 而一个数据结构的设计过程分成抽象层、数据结构层和实现层。
一、Java数据结构之:线性数据结构
线性数据结构:常见的有一维数组,线性表,栈,队列,双队列,串。
![queue](https://i-blog.csdnimg.cn/blog_migrate/762c3fa34f306567f93648124072d8a1.png)
queue
使用场景也非常多,如线程池、MQ、连接池等。
KMP算法: 这个算法一定要牢记,Java数据结构这本书里面针对字符串的查找匹配算法也只介绍了一种。关键点就是:在字符串比对的时候,主串的比较位置不需要回退的问题。
二、Java数据结构之:非线性数据结构
非线性数据结构:常见的有:多维数组,集合,树,图,散列表(hash)。
![Collection](https://i-blog.csdnimg.cn/blog_migrate/47fb4f2a75543c68da792a10934c7243.png)
树形结构,作者觉得它是一种特殊的链形数据结构。最少有一个根节点组成,可以有多个子节点。树,显然是由递归算法组成。
-
在一个树结构中,有且仅有一个结点没有直接父节点,它就是根节点; -
除了根节点,其他结点有且只有一个直接父节点; -
每个结点可以有任意多个直接子节点。
-
1) 自由树/普通树:对子节点没有任何约束。 自由树 -
2) 二叉树:每个节点最多含有两个子节点的树称为二叉树。 2.1) 一般二叉树:每个子节点的父亲节点不一定有两个子节点的二叉树成为一般二叉树。 2.2) 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; 2.3) 满二叉树:所有的节点都是二叉的二叉树成为满二叉树。 二叉树 3) 二叉搜索树/BST:binary search tree,又称二叉排序树、二叉查找树。是有序的。要点:如果不为空,那么其左子树节点的值都小于根节点的值;右子树节点的值都大于根节点的值。 二叉搜索树 3.1) 二叉平衡树:二叉搜索树,是有序的排序树,但左右两边包括子节点不一定平衡,而二叉平衡树是排序树的一种,并且加点条件,就是任意一个节点的两个叉的深度差不多(比如差值的绝对值小于某个常数,或者一个不能比另一个深出去一倍之类的)。这样的树可以保证二分搜索任意元素都是O(log n)的,一般还附带带有插入或者删除某个元素也是O(log n)的的性质。 为了实现,二叉平衡树又延伸出来了一些算法,业界常见的有AVL、和红黑算法,所以又有以下两种树: 3.1.1) AVL树:最早的平衡二叉树之一。应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树。 3.1.2) 红黑树:通过制定了一些红黑标记和左右旋转规则来保证二叉树平衡。 红黑树 红黑树的5条性质:
-
每个结点要么是红的,要么是黑的。 -
根结点是黑的。 -
每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。 -
如果一个结点是红的,那么它的俩个儿子都是黑的。 -
对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。
-
4) B-tree:又称B树、B-树。又叫平衡(balance)多路查找树。树中每个结点最多含有m个孩子(m>=2)。它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点。 B-tree -
4) B+tree:又称B+。是B-树的变体,也是一种多路搜索树。 B+tree 树总结: 树在Java里面应用的也比较多。非排序树,主要用来做数据储存和展示。而排序树,主要用来做算法和运算,HashMap里面的TreeNode就用到了红黑树算法。而B+树在数据库的索引原理里面有典型的应用。
-
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),变换成固定长度的输出,该输出就是散列值。一般通过Hash算法实现。 -
所谓的Hash算法都是散列算法,把任意长度的输入,变换成固定长度的输出,该输出就是散列值(如:MD5,SHA1,加解密算法等)。 -
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
-
我们都知道所有的class都是Object的子类,既所有的class都会有默认Object.java里面的hashCode的方法,如果自己没有重写,默认情况就是native方法通过对象的内存的+对象的值然后通过hash散列算法计算出来个int的数字。 -
最大的特性是:不同的对象,不同的值有可能计算出来的hashCode可能是一样的。
-
Java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表。而Hash表就是综合了这两种数据结构。 -
如:HashTable,HashMap。这个时候就得提一下HashMap的原理了,默认16个数组储存,通过Hash值取模放到不同的桶里面去。(注意:JDK1.8此处算法又做了改进,数组里面的值会演变成树形结构。) -
哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组”。 哈希表
一致性Hash:
![一致性Hash](https://i-blog.csdnimg.cn/blog_migrate/0164dbdef7b37d5075a18a135596ae6b.png)
Java基本算法
空间复杂度:一句来理解就是,此算法在规模为n的情况下额外消耗的储存空间。 时间复杂度:一句来理解就是,此算法在规模为n的情况下,一个算法中的语句执行次数称为语句频度或时间频度。 稳定性:主要是来描述算法,每次执行完,得到的结果都是一样的,但是可以不同的顺序输入,可能消耗的时间复杂度和空间复杂度不一样。
一、二分查找算法
public static void main(String[] args) {
int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};
System.out.println(binSearch(srcArray, 28));
}
/**
* 二分查找普通循环实现
*
* @param srcArray 有序数组
* @param key 查找元素
* @return
*/
public static int binSearch(int srcArray[], int key) {
int mid = srcArray.length / 2;
// System.out.println("=:"+mid);
if (key == srcArray[mid]) {
return mid;
}
//二分核心逻辑
int start = 0;
int end = srcArray.length - 1;
while (start <= end) {
// System.out.println(start+"="+end);
mid = (end - start) / 2 + start;
if (key < srcArray[mid]) {
end = mid - 1;
} else if (key > srcArray[mid]) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
二分查找算法如果没有用到递归方法的话,只会影响CPU。对内存模型来说影响不大。时间复杂度log2n,2的开方。空间复杂度是2。一定要牢记这个算法。应用的地方也是非常广泛,平衡树里面大量采用。
二、递归算法
public static void main(String[] args) {
int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};
System.out.println(binSearch(srcArray, 0,15,28));
}
/**
* 二分查找递归实现
*
* @param srcArray 有序数组
* @param start 数组低地址下标
* @param end 数组高地址下标
* @param key 查找元素
* @return 查找元素不存在返回-1
*/
public static int binSearch(int srcArray[], int start, int end, int key) {
int mid = (end - start) / 2 + start;
if (srcArray[mid] == key) {
return mid;
}
if (start >= end) {
return -1;
} else if (key > srcArray[mid]) {
return binSearch(srcArray, mid + 1, end, key);
} else if (key < srcArray[mid]) {
return binSearch(srcArray, start, mid - 1, key);
}
return -1;
}
递归几乎会经常用到,需要注意的一点是:递归不光影响的CPU。JVM里面的线程栈空间也会变大。所以当递归的调用链长的时候需要-Xss设置线程栈的大小。
三、八大排序算法
-
直接插入排序(Insertion Sort) -
希尔排序(Shell Sort) -
选择排序(Selection Sort) -
堆排序(Heap Sort) -
冒泡排序(Bubble Sort) -
快速排序(Quick Sort) -
归并排序(Merging Sort) -
基数排序(Radix Sort)
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(n²) | O(n) | O(n²) | O(1) |
冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).
/**
* 冒泡排序
*
* ①. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
* ②. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
* ③. 针对所有的元素重复以上的步骤,除了最后一个。
* ④. 持续每次对越来越少的元素重复上面的步骤①~③,直到没有任何一对数字需要比较。
* @param arr 待排序数组
*/
public static void bubbleSort(int[] arr){
for (int i = arr.length; i > 0; i--) { //外层循环移动游标
for(int j = 0; j < i && (j+1) < i; j++){ //内层循环遍历游标及之后(或之前)的元素
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
System.out.println("Sorting: " + Arrays.toString(arr));
}
}
}
}
![快速排序](https://i-blog.csdnimg.cn/blog_migrate/5829bf4e3f95b0f90ec9b9aa08d699a0.png)
![快速排序 In-place](https://i-blog.csdnimg.cn/blog_migrate/2e52c4239a182653503fd0515c746bd1.png)
/**
* 快速排序(递归)
*
* ①. 从数列中挑出一个元素,称为"基准"(pivot)。
* ②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
* ③. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
* @param arr 待排序数组
* @param low 左边界
* @param high 右边界
*/
public static void quickSort(int[] arr, int low, int high){
if(arr.length <= 0) return;
if(low >= high) return;
int left = low;
int right = high;
int temp = arr[left]; //挖坑1:
保存基准的值
while (left < right){
while(left < right && arr[right] >= temp){ //坑2:
从后向前找到比基准小的元素,插入到基准位置坑1中
right--;
}
arr[left] = arr[right];
while(left < right && arr[left] <= temp){ //坑3:
从前往后找到比基准大的元素,放到刚才挖的坑2中
left++;
}
arr[right] = arr[left];
}
arr[left] = temp; //基准值填补到坑3中,准备分治递归快排
System.out.println("Sorting: " + Arrays.toString(arr));
quickSort(arr, low, left-1);
quickSort(arr, left+1, high);
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(nlog₂n) | O(nlog₂n) | O(n²) | O(1)(原地分区递归版) |
![](https://i-blog.csdnimg.cn/blog_migrate/a8b86ca91f4d5ae31d1487c69f5ad4eb.png)
热 文 推 荐
☞区块链+“中国制造”:一文看懂区块链如何提升供应链金融活力与效能!
点击阅读原文,参加中国开发者现状调查!
![](https://i-blog.csdnimg.cn/blog_migrate/af82729b6750d71f1102b847a172b5ea.png)
更多推荐
所有评论(0)