排序算法的稳定性是什么,有什么用?
稳定性,指两个相同元素在排序后的相对顺序。
稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。
I.常见排序算法
时间复杂度中,^符号代表幂次方。
1.冒泡排序
冒泡排序是两层循环,将数组两个元素挨个比较,进行交换。
第一层循环是n,第二层则是n-1,所以时间复杂度为O(n^2)
稳定,因为相同的元素选择不交换即可。
2.选择排序
每次都寻找最大的元素,交换的相应的位置。
和冒泡排序时间复杂度一样,因为循环结构相似,O(n^2)。
不稳定,一般会颠倒位置。
3.插入排序
未排序的元素从已排序的元素中选择位置插入。
和冒泡排序时间复杂度一样,因为循环结构相似,O(n^2)。
稳定,后来的碰到相同元素就往后插入。
4.希尔排序
希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
private static void sort(int[] array) {
int n = array.length;
int h = 1;
while (h<n/3) { //动态定义间隔序列
h = 3*h +1;
}
while (h >= 1) {
for (int i = h; i < n; i++) {
for (int j = i; j >= h && (array[j] < array[j - h]); j -= h) {
int temp = array[j];
array[j] = array[j - h];
array[j-h]= temp;
}
}
h /=3;
}
}
两次循环,有除以3的操作,所以是O(n^1.3)
不稳定,因为数据被打乱了。
5.快速排序
分治法,选中一个元素,把大的和小的各放一边,然后对每一块数据都按此方法做处理。
一次循环决定要处理几次,另外一个是递归操作,所以是O(n*logn)
递归,显然不稳定。
6.归并排序
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。它使用了递归分治的思想;相当于:左半边用尽,则取右半边元素;右半边用尽,则取左半边元素;右半边的当前元素小于左半边的当前元素,则取右半边元素;右半边的当前元素大于左半边的当前元素,则取左半边的元素。
和快排的时间复杂度一样,因为都用了递归,O(n*logn)
稳定,合并序列的时候相同的往后排。
7.堆排序
堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
因为是二叉树,也用到了递归的概念,和快速排序类似,O(n*logn)
不稳定,构建了二叉树。
8.计数排序
找出待排序元素最大最小值,然后开辟数据存储,计算每个元素出现的次数,最后一次排序完毕。
时间复杂度O(n+k)
K很大的时候就不能忽略了,这是O(n+k)的含义。
稳定,倒着输出,搞一个数组的栈即可。看实现方式,如果是只记录出现的次数,最后自己组数组,则不稳定,但如果是看到有存在的元素,将元素取出来,那就是稳定的。
9.桶排序
将数据分到不同的桶里面排序,然后每个再分别排序。
时间复杂度O(n+k)
不稳定,不同的桶遇到相同的值不好处理。
10.基数排序
按每一位进行排序,先从低位开始排序,每一轮遍历,最后排序到最后一位。
时间复杂度O(n+k) 时间复杂度和位数有关系。
稳定。
II.常见数据结构应用
1.链表LRU缓存淘汰
我们维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。
当有一个新的数据被访问时,我们从链表头开始顺序遍历链表。
-
如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
-
如果此数据没有在缓存链表中,又可以分为两种情况:如果此时缓存未满,则将此结点直接插入到链表的头部;如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
这样就实现了一个LRU算法。
2.栈在括号匹配中的作用
当遇到左括号的时候,压入栈,遇到右括号的时候出栈。空栈遇到右括号的话,则表示这个括号对已经不匹配了。
只要最后看栈中的元素是否为空,就能知道括号是否匹配了。
栈也可以实现浏览器的前进后退,前进的时候在后退栈压入数据(同时可能需要出栈),后退的时候出栈,入前进栈。前进时前进栈出栈,入后退栈。
3.检查拼写错误,用什么数据结构实现?
哈希表,不解释。
4.完全二叉树,满二叉树,堆的区别
完全二叉树是一棵树没有光秃秃的树枝,树叶长满了。满二叉树是除了最后一层,其他的树叶都长满了。堆是完全二叉树,且满足每一个节点都大于子节点的值,或小于,即大顶堆和小顶堆的概念。
5.第k大,即排行榜问题
可以用堆,链表的LRU算法感觉也很方便。
6.有向图
微信的好友关系,知乎的关注关系。
III.哈希算法
这里仅介绍下如何解决冲突,哈希算法的概念不多赘述:
1.开放地址法
我们可以对哈希值取mod,或者以其他规则,挨个在内存中探测,若发现某个地址为空,则把数据存入。
2.多次哈希
换一种哈希算法,不可能每次都一样的值,但这样会有效率问题。
3.拉链法
相同哈希值的拉链表,挨个查找。
4.溢出区
将哈希表分为公共和溢出区域,把冲突的哈希值放在另一个地方。