排序
方法比较
方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定??? |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 |
插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 |
希尔排序 | O ( n 1.3 ) O(n^{1.3}) O(n1.3)??? | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 |
归并排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n ) O(n) O(n) | 稳定 |
快速排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n ) O(n) O(n) | 稳定 |
堆排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( 1 ) O(1) O(1) | 不稳定 |
计数排序 | O ( n + k ) O(n+k) O(n+k) | O ( n + k ) O(n+k) O(n+k) | O ( n + k ) O(n+k) O(n+k) | O ( n + k ) O(n+k) O(n+k) | 稳定 |
桶排序 | O ( n + k ) O(n+k) O(n+k) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n + k ) O(n+k) O(n+k) | 稳定 |
基排序 | O ( n k ) O(nk) O(nk) | O ( n k ) O(nk) O(nk) | O ( n k ) O(nk) O(nk) | O ( n + k ) O(n+k) O(n+k) | 稳定 |
具体算法
(1)交换排序–冒泡排序
两层循环,对里层循环的第 j j j和第 j + 1 j+1 j+1个进行比较,如果乱序则交换,里层每循环结束会将最大的放在里层的最后一位
public static int[] bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {//有最后i个固定住了
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
flag = true;
swap(arr,j,j+1)
}
}
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
public int[] swap(int[] nums,int a,int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
return nums;
}
(2)选择排序
两层循环,里层每次选择最小的放在外层循环的开始位
public static int[] selectSort(int[] arr){
for(int i = 0; i < nums.length; i++){
int minIndex = i;;
for(int j = i+1; j < nums.length; j++){
if(nums[j] < nums[minIndex])minIndex = j;
}
nums = swap(nums,i,minIndex);
}
return nums;
}
public int[] swap(int[] nums,int a,int b){
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
return nums;
}
(3)插入排序
将数组看作有序表和无序表,将无序表中元素从后往前扫描,插入有序表中的合适位置
//插入排序
public static int[] insertSort(int[] arr) {
int preIndex,current;
for(int i = 1; i < nums.length; i++){
preIndex = i - 1;
current = nums[i];//保存待插入的值,while循环时将被有序数组占用
while(preIndex >=0 && nums[preIndex] > current){
nums[preIndex + 1] = nums[preIndex];//将大于待插入的数往后挪动
preIndex --;
}
nums[preIndex + 1] = current;
}
return nums;
}
(4)希尔排序
step相同分为同一组,在同一组中先进行排序将数据小的尽量放前面,然后二分减小step来进行更多数据的排序,在同组排序中可以使用冒泡排序法(交换法)和插入排序(移位法)
交换法:
//冒泡排序法
public static void shellsortexchane(int[] arr) {
int step = arr.length / 2;
int tmp;
while (step > 0) {//针对步长进行循环
for (int i = 0; i < arr.length; i++) {//每次+1,挨着堆每组元素进行插入排序
for (int j = i + step; j < arr.length; j = j + step) {//在当组中依次将同一步长组内的两两交换排序
//每组数据为arr[i],arr[i+step],arr[i+2*step],...
if (arr[i] > arr[j]) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
step = step / 2;
}
}
移位法:
//插入法
public static void shellsortinsert(int[] arr) {
int step = arr.length / 2;
int tmp;
while (step > 0) {//针对步长进行循环
for (int i = 0; i < arr.length - step; i++) {//开始插入排序
int j = i + step;
//每组数据为arr[i],arr[i+step],arr[i+2*step],...
tmp = arr[j];
while (j >= step && arr[j - step] > tmp) {
arr[j] = arr[j - step];
j = j - step;
}
arr[j] = tmp;
}
step = step / 2;
}
}
(5)归并排序
先进行二分,然后将左右两边数组合并
public int[] sortArray(int[] nums) {
mergeSort(0,nums.length - 1,nums);
return nums;
}
public void mergeSort(int left, int right,int[] nums){
if(left >= right)return;
int mid = (left + right) / 2;
mergeSort(left,mid,nums);
mergeSort(mid+1,right,nums);
combine(left,mid,right,nums);
}
public void combine(int left, int mid, int right,int[] nums){
//使用辅助数组
int[] res = new int[nums.length];
for(int i = 0; i < nums.length; i++){
res[i] = nums[i];
}
//左右数组开始的下标
int l = left;
int r = mid+1;
int count = left;//记录合并的下标
while(l <= mid && r <= right){
if(res[l] < res[r]){
nums[count] = res[l];
l ++;
}else{
nums[count] = res[r];
r++;
}
count ++;
}
//剩余数组
while(r <= right){
res[count] = nums[r];
r++;
count ++;
}
while(l <= mid){
nums[count] = res[l];
l++;
count ++;
}
}
(6 )快速排序
数组中小于等于pivot的数在[0,i],大于pivot的数在[i+1,j]区间
public void quickSort(int[] nums,int left,int right){
if(left < right){
int pivot = partation(nums,left,right);
quickSort(nums, left , pivot - 1);
quickSort(nums, pivot + 1, right);
}
}
public int partition(int[] nums,int left,int right){
int pivot = right;
int pre = left - 1;//指向小于pivot值的最后一个
for(int i = left; i < right; i ++){
if(nums[i] <= nums[pivot]){
pre ++;
if(i != pre)swap(nums,pre,i);
}
}
pre ++;
if(pre < right && nums[pre] > nums[right])swap(nums,pre,right);
return pre;
}
public void swap(int[] nums,int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
(7)堆排序
堆:子结点的值总是大于(小于)父结点
思想:
1)构建初始堆,将待排序列构成一个大顶堆(或者小顶堆),升序大顶堆,降序小顶堆;
2)将堆顶元素与堆尾元素交换,并断开(从待排序列中移除)堆尾元素。
3)重新构建堆。
4)重复2~3,直到待排序列中只剩下一个元素(堆顶元素)。
public int[] sortArray(int[] nums) {
//创建堆
for (int i = (nums.length - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(nums, i, nums.length);
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = nums.length - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = nums[i];
nums[i] = nums[0];
nums[0] = temp;
//重新对堆进行调整
adjustHeap(nums, 0, i);
}
return nums;
}
private static void adjustHeap(int[] arr, int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr[lChild]) {
break;
}
// 把孩子结点的值赋给父结点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
(8)计数排序
不是基于比较,适用于输入数据为一定范围内的整数,如题leedcode75. 颜色分类
算法思路:
1)找到输入数据的最大和最小值
2)统计数组中每个值为i的数出现的次数,存入数组C的第i项
3)对所有的计数累加,对C从第一个数开始,每项和前一项相加
4)反向填充目标数组:将每个元素i放在心数组的第C[i]想,每放一个元素将C[i]-1
leedcode75. 颜色分类:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
public void sortColors(int[] nums) {
//根据最大最小确定统计数组大小
int[] colors = new int[3];
//统计每一项的个数到统计数组里
for(int i = 0; i < nums.length; i++){
colors[nums[i]] ++;
}
//填充统计数组到原来的nums
int count = 0;
for(int i = 0; i < nums.length; i++){
while(colors[count] <= 0){
count ++;
}
nums[i] = count;
colors[count] --;
}
}
(9)桶排序
假设数据服从均匀分布,将数据分到有限的桶里,然后在桶内分布排序
算法思路:
1)设置一个定量的数组当作空桶;
2)遍历输入数据,并且把数据一个一个放到对应的桶里去;
3)对每个不是空的桶进行排序;
4)从不是空的桶里把排好序的数据拼接起来。
例题:164. 最大间距、451. 根据字符出现频率排序、692. 前K个高频单词
例题451. 根据字符出现频率排序:给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
public String frequencySort(String s) {
HashMap<Character,Integer> map = new HashMap<>();
//统计词频记录在map中
for(int i = 0; i < s.length(); i++){
map.put(s.charAt(i), map.getOrDefault(s.charAt(i),0) + 1);
}
// 构造桶的集合
List<Character>[] buckets = new List[s.length()];
// 将频率为i的字符放到第i-1个桶里
for(char key : map.keySet()){
int times = map.get(key);
if(buckets[times - 1] == null)buckets[times - 1] = new ArrayList<Character>();
buckets[times - 1].add(key);
}
//将字符拼接到一起
StringBuilder res = new StringBuilder();
for(int i = buckets.length - 1; i >= 0; i--){//先高频
if(buckets[i] != null){
for (char c : buckets[i]) {// 遍历桶里的每个字符
// System.out.println(c +" "+ i);
for(int j = 0; j <= i; j++){ // 每个字符出现了i+1次
res.append(c);
}
}
}
}
return res.toString();
}
692. 前K个高频单词:给一非空的单词列表,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
public List<String> topKFrequent(String[] words, int k) {
HashMap<String,Integer> map = new HashMap<>();
//统计词频记录在map中
for(int i = 0; i < words.length; i++){
map.put(words[i], map.getOrDefault(words[i],0) + 1);
}
// 构造桶的集合
List<String>[] buckets = new List[words.length];
// 将频率为i的字符放到第i-1个桶里
for(String key : map.keySet()){
int times = map.get(key);
if(buckets[times - 1] == null)buckets[times - 1] = new ArrayList<String>();
buckets[times - 1].add(key);
}
// 对桶内字符进行排序
for(int i = 0; i < buckets.length; i++){
if(buckets[i] != null){
Collections.sort(buckets[i]);
// System.out.println(buckets[i]);
}
}
//将字符合并输出
List<String> res = new ArrayList<String>();
int i = buckets.length - 1;
for(; i >=0; i--){
if(buckets[i] != null){
for(String str : buckets[i]){
if(k > 0){
res.add(str);
k--;
}else{
return res;
}
}
}
}
return res;
}
(10)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
算法思路:
1)取得数组中最大的数,并取得位数
2)arr为原始数组,从最低位开始取每个位组成radix数组
3)对radix进行计数排序