排序算法总结

目录

1.快速排序

理论部分

2.归并排序

理论部分

3.插入排序

理论部分

4.冒泡排序

理论部分

5.选择排序

理论部分

6.桶排序

347. Top K Frequent Elements (Medium)

7.堆排序

理论部分

8.例子

215. Kth Largest Element in an Array

快速选择

归并

根堆

347. Top K Frequent Elements (Medium)

 法一桶排序已经在理论部分讲过了

堆排序加优先队列(或者就叫小根堆)

451. Sort Characters By Frequency (Medium)

桶排序

 75. Sort Colors (Medium)


1.快速排序

理论部分

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] nums={11,24,5,32,50,34,54,76};
        System.out.println("快速排序前:"+ Arrays.toString(nums));
        quickSort(nums,0,nums.length-1);
        System.out.println("快速排序后:"+ Arrays.toString(nums));
    }
    public static void quickSort(int[] nums, int start, int end){
        if(start>end) return;
        int i,j,base;
        i=start;
        j=end;
        base=nums[start];
        while (i<j){
            while (i<j && nums[j]>=base) j--;
            while (i<j && nums[i]<=base) i++;
            if(i<j){
                swap(nums,i,j);
            }
        }
        swap(nums,start,i);
        quickSort(nums,start,j-1);
        quickSort(nums,j+1,end);
    }
    public static void swap(int[] nums,int left,int right){
        int temp=nums[left];
        nums[left]=nums[right];
        nums[right]=temp;
    }
}

快速排序(详细讲解)_梦里Coding的博客-CSDN博客_快速排序

这个讲的非常好,建议就看这个看明白就好了

2.归并排序

理论部分

这一个比较好理解:

int* Merge_Sort(int arr[], int start, int end) {
    //当start==end时,此时分组里只有一个元素,所以这是临界点
    if (start < end) {
        //分成左右两个分组,再进行递归
        int mid = (start + end) / 2;
        //左半边分组
        Merge_Sort(arr, start, mid);
        //右半边分组
        Merge_Sort(arr, mid + 1, end);
        //递归之后再归并归并一个大组
        Merge(arr, start, mid, end);
    }
    return arr;
}

void Merge(int arr[], int start, int mid, int end) {
    //左边分组的起点i_start,终点i_end,也就是第一个有序序列
    int i_start = start;
    int i_end = mid;
    //右边分组的起点j_start,终点j_end,也就是第二个有序序列
    int j_start = mid + 1;
    int j_end = end;
    //额外空间初始化,数组长度为end-start+1
    int *temp = new int[end - start + 1];
    int len = 0;
    //合并两个有序序列
    while (i_start <= i_end && j_start <= j_end) {
        //当arr[i_start]<arr[j_start]值时,将较小元素放入额外空间,反之一样
        if (arr[i_start] < arr[j_start]) {
            temp[len] = arr[i_start];
            len++;
            i_start++;
        }
        else {
            temp[len] = arr[j_start];
            len++;
            j_start++;
        }
        //temp[len++]=arr[i_start]<arr[j_start]?arr[i_start++]:arr[j_start++];
    }

    //i这个序列还有剩余元素
    while (i_start <= i_end) {
        temp[len] = arr[i_start];
        len++;
        i_start++;
    }
    //j这个序列还有剩余元素
    while (j_start <= j_end) {
        temp[len] = arr[j_start];
        len++;
        j_start++;
    }
    //辅助空间数据覆盖到原空间
    for (int i = 0; i < end - start + 1; i++) {
        arr[start + i] = temp[i];
    }
}

图解归并排序,带你彻底了解清楚!_程序员的时光的博客-CSDN博客_归并排序这个教程很推荐

然后下面是简洁版的:

void merge_sort(vector<int>& nums, int l, int r, vector<int>& temp) {
    if (l + 1 >= r) {
        return;
    }
    // divide
    int m = l + (r - l) / 2;
    merge_sort(nums, l, m, temp);
    merge_sort(nums, m, r, temp);
    // conquer
    int p = l, q = m, i = l;
    while (p < m || q < r) {
        if (q >= r || (p < m && nums[p] <= nums[q])) {
            temp[i++] = nums[p++];
        }
        else {
            temp[i++] = nums[q++];
        }
    }
    for (i = l; i < r; ++i) {
        nums[i] = temp[i];
    }
}

3.插入排序

理论部分

void insertion_sort(vector<int> &nums, int n) {
    for (int i = 0; i < n; ++i) {
    for (int j = i; j > 0 && nums[j] < nums[j-1]; --j) {
        swap(nums[j], nums[j-1]);
} } }

4.冒泡排序

理论部分

public class BubbleSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        for (int i = 1; i < arr.length; i++) {
            // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
            boolean flag = true;

            for (int j = 0; j < arr.length - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;

                    flag = false;
                }
            }

            if (flag) {
                break;
            }
        }
        return arr;
    }
}

c++的是这样:

void bubble_sort(vector<int>& nums, int n) {
    bool swapped;
    for (int i = 1; i < n; ++i) {
        swapped = false;
        for (int j = 1; j < n - i + 1; ++j) {
            if (nums[j] < nums[j - 1]) {
                swap(nums[j], nums[j - 1]);
                swapped = true;
            }
        }
        if (!swapped) {
            break;
        }
    }
}

5.选择排序

理论部分

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     // 寻找最小的数
                minIndex = j;                 // 将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

6.桶排序

347. Top K Frequent Elements (Medium)

用例子说明理论,关键难点在于一个嵌套装桶

题解
        顾名思义,桶排序的意思是为每个值设立一个桶,桶内记录这个值出现的次数(或其它属 性),然后对桶进行排序。针对样例来说, 我们先通过桶排序得到四个桶 [1,2,3,4],它们的值分别 为 [4,2,1,1],表示每个数字出现的次数。
        
        紧接着,我们对桶的频次进行排序,前 k 大个桶即是前 k 个频繁的数。这里我们可以使用各种 排序算法,甚至可以再进行一次桶排序,把每个旧桶根据频次放在不同的新桶内。针对样例来说, 因为目前最大的频次是 4 我们建立 [1,2,3,4] 四个新桶,它们分别放入的旧桶为 [[3,4],[2],[],[1]], 表示不同数字出现的频率。最后,我们从后往前遍历,直到找到 k 个旧桶。
vector<int> topKFrequent1(vector<int>& nums, int k) {
	unordered_map<int, int> counts;
	int max_count = 0;
	for (const int& num : nums) {
		max_count = max(max_count, ++counts[num]);//通过key找到value
		//这里其实是map添加元素方法里面的赋值添加,没有通过insert
	}
	vector<vector<int>> buckets(max_count + 1);
	for (const auto& p : counts) {
		buckets[p.second].push_back(p.first);
	}
	vector<int> ans;
	for (int i = max_count; i >= 0 && ans.size() < k; --i) {
		for (const int& num : buckets[i]) {
			ans.push_back(num);
			if (ans.size() == k) {
				break;
			}
		}
	}
	return ans;
}

注意,桶排序的话,map要先学会。

7.堆排序

理论部分

建议阅读大话数据结构,上面讲的非常清楚,由于本文章用于笔记而非教程,就不赘述了。

下面是大根堆的代码:

//大根堆
void HeapAdjust(vector<int>& r, int s, int m)
{
    int temp, j;
    temp = r[s];
    for (j = 2 * s; j <= m; j *= 2) /* 沿关键字较大的孩子结点向下筛选 */
    {
        if (j < m && r[j] < r[j + 1])
            ++j; /* j为关键字中较大的记录的下标 */
        if (temp >= r[j])
            break; /* rc应插入在位置s上 */
        r[s] = r[j];
        s = j;
    }
    r[s] = temp; /* 插入 */
}


void swap(vector<int>& r, int i, int j) {
    r[i] = r[i] ^ r[j];
    r[j] = r[i] ^ r[j];
    r[i] = r[i] ^ r[j];
}

void HeapSort(vector<int>& r, int len)
{
    int i;
    for (i = len / 2; i >= 0; i--) /*  把L中的r构建成一个大顶堆 */
        HeapAdjust(r, i, len);

    for (i = len; i > 0; i--)
    {
        swap(r, 0, i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */
        HeapAdjust(r, 0, i - 1); /*  将L->r[1..i-1]重新调整为大顶堆 */
    }
}

小根堆要是不用stl会很麻烦.......

8.例子

215. Kth Largest Element in an Array

 这个问题有多个方法,一个一个说:

快速选择

快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求 解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢( pivot )即可,不需要对其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O (n2),我们这里为了方便省略掉了打乱的步骤。

 需要好好体会。


void swap(vector<int>&nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}
void quickSort(vector<int>& nums, int start, int end) {
    if (start > end) return;
    int i, j, base;
    i = start;
    j = end;
    base = nums[start];
    while (i < j) {
        //一定要先动j
        while (i < j && nums[j] >= base) j--;
        while (i < j && nums[i] <= base) i++;
        if (i < j) {
            swap(nums, i, j);
        }
    }
    swap(nums, start, i);
    quickSort(nums, start, j - 1);
    quickSort(nums, j + 1, end);
}

int findKthLargest2(vector<int>& nums, int k) {
    quickSort(nums, 0, nums.size() - 1);
    int i = nums.size() - 1;
    while (k > 1) {
        i--;
        k--;
    }
    return nums[i];

}

归并


void Merge_Sort(vector<int>& arr, int start, int end) {
    //当start==end时,此时分组里只有一个元素,所以这是临界点
    if (start < end) {
        //分成左右两个分组,再进行递归
        int mid = (start + end) / 2;
        //左半边分组
        Merge_Sort(arr, start, mid);
        //右半边分组
        Merge_Sort(arr, mid + 1, end);
        //递归之后再归并归并一个大组
        Merge(arr, start, mid, end);
    }

}

void Merge(vector<int>& arr, int start, int mid, int end) {
    //左边分组的起点i_start,终点i_end,也就是第一个有序序列
    int i_start = start;
    int i_end = mid;
    //右边分组的起点j_start,终点j_end,也就是第二个有序序列
    int j_start = mid + 1;
    int j_end = end;
    //额外空间初始化,数组长度为end-start+1
    int* temp = new int[end - start + 1];
    int len = 0;
    //合并两个有序序列
    while (i_start <= i_end && j_start <= j_end) {
        //当arr[i_start]<arr[j_start]值时,将较小元素放入额外空间,反之一样
        if (arr[i_start] < arr[j_start]) {
            temp[len] = arr[i_start];
            len++;
            i_start++;
        }
        else {
            temp[len] = arr[j_start];
            len++;
            j_start++;
        }
        //!!!!!!!!!!!!!!!!!!!!!!!!!!    temp[len++]=arr[i_start]<arr[j_start]?arr[i_start++]:arr[j_start++];
    }

    //i这个序列还有剩余元素
    while (i_start <= i_end) {
        temp[len] = arr[i_start];
        len++;
        i_start++;
    }
    //j这个序列还有剩余元素
    while (j_start <= j_end) {
        temp[len] = arr[j_start];
        len++;
        j_start++;
    }
    //辅助空间数据覆盖到原空间
    for (int i = 0; i < end - start + 1; i++) {
        arr[start + i] = temp[i];
    }
}

int findKthLargest1(vector<int>& nums, int k) {
    Merge_Sort(nums, 0, nums.size() - 1);
    int i = nums.size() - 1;
    while (k > 1) {
        i--;
        k--;
    }
    return nums[i];

}

根堆

void HeapAdjust(vector<int>& r, int s, int m)
{
    int temp, j;
    temp = r[s];
    for (j = 2 * s; j <= m; j *= 2) /* 沿关键字较大的孩子结点向下筛选 */
    {
        if (j < m && r[j] < r[j + 1])
            ++j; /* j为关键字中较大的记录的下标 */
        if (temp >= r[j])
            break; /* rc应插入在位置s上 */
        r[s] = r[j];
        s = j;
    }
    r[s] = temp; /* 插入 */
}


void swap(vector<int>& r, int i, int j) {
    r[i] = r[i] ^ r[j];
    r[j] = r[i] ^ r[j];
    r[i] = r[i] ^ r[j];
}

void HeapSort(vector<int>& r, int len)
{
    int i;
    for (i = len / 2; i >= 0; i--) /*  把L中的r构建成一个大顶堆 */
        HeapAdjust(r, i, len);

    for (i = len; i > 0; i--)
    {
        swap(r, 0, i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */
        HeapAdjust(r, 0, i - 1); /*  将L->r[1..i-1]重新调整为大顶堆 */
    }
}


int findKthLargest(vector<int>& nums, int k) {
    HeapSort(nums, nums.size() - 1);
    int i = nums.size() - 1;
    while (k > 1) {
        i--;
        k--;
    }

    return nums[i];

}

347. Top K Frequent Elements (Medium)

 法一桶排序已经在理论部分讲过了

我们来看法二:

堆排序加优先队列(或者就叫小根堆,利用stl的priority_queue)

其实前k个高频或者低频的问题就要想到用根堆
这里就需要小根堆
求前 k 大,用小根堆,求前 k 小,用大根堆。
map是很合适的一种存储结构,key就是元素,value就是出现次数

思路是这样:
我们之前不是学了把一个数组构建成堆嘛
那在这里我们先把数据存入map然后其实关键是对value进行排序,然后输出对应的key
不过,既然要求前k个高频的,我们不需要对map所有的元素进行排序(nlogn),我们只要维护前K个高频


说到这里是不是有想法了,我们以大根堆为例,用完全二叉树来编号,我们限制编号最大为k
我们对map进行遍历,每次来判断是不是要加入堆里面,要加就加进去,并且要维持大根堆的形状


这里为什么要用小根堆?大根堆不是顶点最大吗,好像要用大顶堆
定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了(加入元素都加在末尾)


 一般弹出都是弹出顶


所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。


这样时间复杂度就是n*logk,因为二叉树只维护k个元素


然后这里还有一个现成的数据结构,叫优先队列  priority_queue

 时间复杂度:O(nlogk)
 空间复杂度:O(n)


topk (前k大)用小根堆,维护堆大小不超过 k 即可。每次压入堆前和堆顶元素比较,如果比堆顶元素还小,直接扔掉,否则压入堆。检查堆大小是否超过 k,如果超过,弹出堆顶。复杂度是 nlogk 避免使用大根堆,因为你得把所有元素压入堆,复杂度是 nlogn,而且还浪费内存。如果是海量元素,那就挂了。
[注意]

求前 k 大,用小根堆,求前 k 小,用大根堆。

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;/*注意这个是大于,这和底层实现有关,反正就是用到了查一查就好,没必要记*/
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒叙来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

451. Sort Characters By Frequency (Medium)

桶排序

体会叠加

string frequencySort(string s) {
	unordered_map  <char, int> count;
	int max_count = 0;
	for (int i = 0; i < s.size(); ++i) {
		++count[s[i]];
		max_count = max(max_count, count[s[i]]);
	}
	//这种叠加的,真的要体会
	vector<string> buckets(max_count );
	for (const auto& p : count) {
		int temp = p.second;
		while (temp > 0) {
			buckets[p.second - 1].push_back(p.first);
			--temp;
		}
	}

	string ans;
	for (int i = max_count - 1; i >= 0; --i) {
		ans.append(buckets[i]);
	}


	return ans;
}

 75. Sort Colors (Medium)

特色是只有012,所以可以写出更简洁的代码

先看通用的快速排序:


void swap(vector<int>& nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}
void quickSort(vector<int>& nums, int start, int end) {
    if (start > end) return;
    int i, j, base;
    i = start;
    j = end;
    base = nums[start];
    while (i < j) {
        //一定要先动j
        while (i < j && nums[j] >= base) j--;
        while (i < j && nums[i] <= base) i++;
        if (i < j) {
            swap(nums, i, j);
        }
    }
    swap(nums, start, i);
    quickSort(nums, start, j - 1);
    quickSort(nums, j + 1, end);
}
void sortColors(vector<int>& nums) {
    quickSort(nums, 0, nums.size() - 1);


}

 更简洁的:


//扫描一遍的
void sortColors2(vector<int>& nums, int len) {

    int r1 = -1;
    int r2 = -1;
    for (int i = 0; i < len; i++) {
        if (nums[i] < 2)
        {
            r2++;
            swap(nums, i, r2);
            if (nums[r2] < 1)
            {
                r1++;
                swap(nums, r1, r2);
            }
        }

    }
}
class Solution {
public:
    void sortColors(int nums[], int len) {
        int num0 = 0, num1 = 0, num2 = 0;
        for (int i = 0; i < len; i++) {
            if (nums[i] == 0) {
                nums[num2++] = 2;
                nums[num1++] = 1;
                nums[num0++] = 0;
            }
            else if (nums[i] == 1) {
                nums[num2++] = 2;
                nums[num1++] = 1;
            }
            else {
                nums[num2++] = 2;
            }
        }
    }
};

注意:还有希尔排序、基数排序等,个人认为熟练掌握以上排序已经完全足够,以后有时间会继续补充的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值