弄懂所有排序算法(C++)

快速排序(基于划分的思想)

// 该函数实现了快速排序算法
void quick_sort(vector<int> &nums, int l, int r) 
{

//1.定义一个出口(当l+1>=r时)
	  // 当l大于等于r时,结束排序
	  if (l + 1 >= r) {
		    return;
	  }


  // 设置首尾指针和关键元素
  int first = l, last = r - 1, key = nums[first];

//2.小的放左边
//3.大的放右边

  // 当first小于last时,执行循环
  while (first < last){
    // 移动尾,寻找小于key的元素
    while (first < last && nums[last] >= key) 
      --last;
    // 将小于key的元素放到数组的左边
    nums[first] = nums[last];    
    // 移动头,寻找大于key的元素
    while (first < last && nums[first] <= key) {
      ++first;
    }
    // 将大于key的元素放到数组的右边
    nums[last] = nums[first];
  }
//4.key放到正确位置
  // 把key放到正确的位置,此时,key左边的元素都小于它,右边的元素都大于它
  nums[first] = key;
//5.对左边元素递归
//6.对右边元素递归
  // 递归地对key的左边元素进行快速排序
  quick_sort(nums, l, first);
  
  // 递归地对key的右边元素进行快速排序
  quick_sort(nums, first + 1, r);
}




归并排序(基于合并的思想)


void merge_sort(vector<int> &nums, int l, int r, vector<int> &temp) {
    // 基本情况:如果待排序数组的长度小于等于1,那么不需要排序,直接返回
    if (l + 1 >= r) {
        return;
    }
//1.不断的进行划分,直到只剩一个元素
    // 分治排序的“分”步骤
    // 计算中点m,将数组一分为二,分别为[l, m)和[m, r)
    int m = l + (r - l) / 2;
    
    // 递归对左半部分进行排序
    merge_sort(nums, l, m, temp);
    // 递归对右半部分进行排序
    merge_sort(nums, m, r, temp);


//2.对左右两个子序列进行合并排序
    // 分治排序的“治”步骤
    // 开始合并两个已排序的子数组
    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];
    }
}


插入排序(从头到尾不断往前插入)

插入排序是一种简单的排序算法,它的工作原理是通过构建有序序列,对未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

主要思路:
默认从第一个元素开始往右挨个看,每个元素向左逐个对比,直到向右走完所有元素。

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]);
			     } 
		} 
}


冒泡排序(一次冒一个泡(最大/最小数))

冒泡排序被这样命名是因为,随着排序过程的进行,最大的元素(如果是升序排序)或者最小的元素(如果是降序排序)就像水中的"气泡"一样,慢慢"浮"到数组的顶端或者尾部。

在冒泡排序的每一轮排序过程中,都会通过两两比较并交换位置,使得当前最大(或最小)的元素移动到正确的位置。这个过程就像是气泡在水中不断上浮的过程,因此这种排序算法被称为"冒泡排序"。

冒泡排序的一个特点是越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端,故名冒泡排序。
冒泡:最大或最小的一个个的冒到水上
主要步骤:
1.冒出一个泡(从第一个开始,向右)
2.进行冒泡操作(将一个泡层层的冒出,向右)

void bubble_sort(vector &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;
	}
}
															

鸡尾酒排序(双冒泡法)

思路:冒泡排序为从前往后捋,鸡尾酒排序为前后一起捋

void cocktail_sort(vector<int>&nums)
{
	int start = 0, end = nums.size()-1;
	bool swaped = true;
	while (swaped)
	{
		swaped = false;
		for (int i = start; i < end; ++i)
		{
			if (nums[i] > nums[i + 1])
			{
				swap(nums[i], nums[i + 1]);
				swaped = true;
			}
		}

		if (!swaped)
			break;
		swaped = false;
		end--;
		for (int i = end; i > start; --i)
		{
			if (nums[i] < nums[i-1])
			{
				swap(nums[i - 1], nums[i]);
				swaped = true;
			}
		}
		start++;

	}
}
 

选择排序(不断挑最小)

选择排序是一种简单直观的排序算法,其基本思想是从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余的未排序元素中寻找到最小(或最大)元素,然后放到已排序的序列的末尾。以此类推,持续进行此过程,直到全部待排序的数据元素排完。
注意
选择排序的效率较低,因为它每次都要从未排序的部分扫描最小或者最大元素,然后进行交换,这样会导致大量的比较和交换操作。

操作思路:
1.从左向右一个个元素挨个拿出来
2.对于拿出来的元素,挨个向右对比找到比拿出元素还小的(找最小的过程,必须有一个索引来锁定住最小的变量)
3.对于找出的较小元素与拿出元素交换位置

void selection_sort(vector &nums, int n) 
{
	int mid;
	for (int i = 0; i < n - 1; ++i)     //最后一个元素没有必要在排序
	{
		mid = i;
		for(int j = i + 1; j < n; ++j)    //找到最小的单个元素
		{
			if (nums[j] < nums[mid]) 
				mid = j;
		}
		swap(nums[mid], nums[i]);
	}
}

快速选择

例子

题目描述
在一个未排序的数组中,找到第 k 大的数字。
输入输出样例
输入一个数组和一个目标值 k,输出第 k 大的数字。题目默认一定有解。
Input: [3,2,1,5,6,4] and k = 2
Output: 5
题解
快速选择一般用于求解 k-th Element 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求 解工作。快速选择的实现和快速排序相似,不过只需要找到第 k 大的枢(pivot)即可,不需要对 其左右再进行排序。与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂 度为 O(n 2 ),我们这里为了方便省略掉了打乱的步骤。

  • 这是快速排序算法的一个变种,用于在未排序的数组中查找第 k 个最大(或最小)的元素。
  • 快速选择的基本思想与快速排序类似,都是选择一个“基准”元素,然后将数组分为两部分:一部分的元素都比基准小,另一部分的元素都比基准大。
  • 但与快速排序不同的是,我们只需要处理与 k 有关的那一部分。

这种方法的平均时间复杂度是 O(n)。但在最差情况下,它可能会退化到 O(n^2)。为了防止这种情况发生,通常会使用随机化技巧。

1.进行折半选择,比目标值大就向左,比目标值小就向右
2.使用一个快速选择算法,随便定一个基准,然后把小的放左边大的放右边

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int l = 0, r = nums.size() - 1, mid, target = nums.size() - k;
        while (l < r) {
            mid = quick(nums, l, r);
            if (mid == target)
                return nums[mid];
            else if (mid < target)
                l = mid + 1;
            else
                r = mid - 1;
        }
        return nums[l];
    }

    int quick(vector<int>& nums, int l, int r) {
        int i = l + 1, j = r;
        while (i <= j) {
            while (i <= j && nums[i] <= nums[l]) // find smaller than l
                ++i;
            while (i <= j && nums[j] >= nums[l]) // find larger than l
                --j;
            if (i < j) swap(nums[i], nums[j]);
        }
        swap(nums[l], nums[j]);
        return j;
    }
};

桶排序

桶排序是一个分发式的排序算法,它将数组分成多个区间(或“桶”),然后对每个桶中的数据进行排序。最后,再将所有桶的数据按桶的顺序连接起来,得到排序后的序列。它通常用在输入数据是均匀分布的场合。

以下是桶排序的基本思想:

  1. 确定桶的数量: 根据数据的特性和分布范围,确定要创建的桶的数量。

  2. 分配到桶中: 遍历输入数据,将每个数据放到对应的桶中。数据应该被分配到范围内对应的桶。例如,如果数据范围是[0,100)并且我们有10个桶,那么数字范围[0,10)的数据将被放入第一个桶,范围[10,20)的数据放入第二个桶,以此类推。

  3. 排序每个桶: 对每个桶中的数据进行排序。这可以使用其他排序算法,如插入排序。

  4. 合并桶: 一旦所有的桶都被排序,就按照桶的顺序将数据合并回一个数组。

性质与用途:

  • 桶排序是一个线性时间的排序算法,当满足以下条件时:
    • 输入数据是均匀分布的。
    • 桶的数量与输入数据的数量是线性关系。
  • 桶排序适用于待排序数据可以均匀、独立地分布到各个桶中的场合。
  • 在实际应用中,桶排序可以用来分布大量数据,如通过IP地址进行排序、按照地理位置进行排序等。

示例:

假设有一个数组:[4.5, 0.84, 3.25, 2.18, 0.5],我们想使用桶排序对其进行排序。

  1. 由于数组中的所有数字都是在0到5之间的,我们可以使用5个桶。
  2. 将这些数字分配到对应的桶中:
    • 0.5和0.84放入第一个桶。
    • 无数字在1和2之间,所以第二个桶为空。
    • 2.18放入第三个桶。
    • 3.25放入第四个桶。
    • 4.5放入第五个桶。
  3. 对每个桶中的数字进行排序(这里每个桶的数字数量很少,所以不需要额外排序)。
  4. 合并所有桶中的数据,得到排序后的数组:[0.5, 0.84, 2.18, 3.25, 4.5]

注意:在实际应用中,选择桶的数量和桶的大小是关键。太少或太多的桶都可能导致算法效率降低。

例子

给定一个数组,求前 k 个最频繁的数字。

输入是一个数组和一个目标值 k。输出是一个长度为 k 的数组。
Input: nums = [1,1,1,1,2,2,3,4], k = 2
Output: [1,2]
顾名思义,桶排序的意思是为每个值设立一个桶,桶内记录这个值出现的次数(或其它属 性),然后对桶进行排序。针对样例来说,我们先通过桶排序得到四个桶 [1,2,3,4],它们的值分别 为 [4,2,1,1],表示每个数字出现的次数。

vector<int> topKFrequent(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]);
    }

    // 桶排序的核心部分
    // 创建一个桶的数组
    vector<vector<int>> buckets(max_count + 1);   //为了使索引和桶数相对应,所以+1

//将对应频率的数字放入桶中,填充完后桶即为有序(利用空间来排序(通过频率与空间顺序的结合))
    // 填充桶,buckets[p.second]表示出现次数为p.second的所有元素
    for (const auto & p : counts) {
        buckets[p.second].push_back(p.first);
    }

    // 用于存放结果的数组
    vector<int> ans;

//拿出桶来
    // 从出现次数最高的桶开始,取出前k个桶的所有元素
    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;
}

Sort Characters By Frequency

Given a string s, sort it in decreasing order based on the frequency of the characters. The frequency of a character is the number of times it appears in the string.

Return the sorted string. If there are multiple answers, return any of them.

Example 1:

Input: s = “tree”
Output: “eert”
Explanation: ‘e’ appears twice while ‘r’ and ‘t’ both appear once.
So ‘e’ must appear before both ‘r’ and ‘t’. Therefore “eetr” is also a valid answer.

Example 2:

Input: s = “cccaaa”
Output: “aaaccc”
Explanation: Both ‘c’ and ‘a’ appear three times, so both “cccaaa” and “aaaccc” are valid answers.
Note that “cacaca” is incorrect, as the same characters must be together.

Example 3:

Input: s = “Aabb”
Output: “bbAa”
Explanation: “bbaA” is also a valid answer, but “Aabb” is incorrect.
Note that ‘A’ and ‘a’ are treated as two different characters.

Constraints:

  • 1 <= s.length <= 5 * 105
  • s consists of uppercase and lowercase English letters and digits.
 				  
class Solution {
public:
    string frequencySort(string s) {
        int max_count = 0;
        unordered_map<char,int> count;
        
        // 1. 遍历字符串s,计算每个字符出现的次数,并找到最大次数。
        for(char c : s) {
            max_count = max(max_count, ++count[c]);
        }

        // 2. 使用桶排序思想,创建一个大小为字符串s的长度+1的向量。
        vector<vector<char>> bucket(s.size() + 1);

        // 3. 根据每个字符出现的次数,将其放入对应的桶中。
        for(auto &p : count) {
            bucket[p.second].push_back(p.first);
        }

        string ans;
        // 4. 从最大次数开始,按频率从高到低的顺序将字符添加到答案字符串中。
        for(int i = max_count; i > 0; --i) {
            int k = bucket[i].size() - 1;
            // 对于每个桶,考虑其中的每个字符。
            while(k >= 0) {
                int j = i;
                // 将每个字符重复i次,即它在字符串s中出现的次数。
                while(j-- > 0) {
                    ans += bucket[i][k];
                }
                k--;
            }
        }
        return ans;  // 返回重新排序后的字符串。
    }
};




Sort Colors

Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.

We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively.

You must solve this problem without using the library’s sort function.

Example 1:

Input: nums = [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]

Example 2:

Input: nums = [2,0,1]
Output: [0,1,2]

Constraints:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i] is either 0, 1, or 2.

希尔排序

它首先选择一个“增量”或“间隔”(在这个实现中,间隔是数组长度的一半,并在每次迭代中减半)。然后,它对间隔内的所有元素进行插入排序。这一过程重复,每次都将间隔减小,直到间隔为1,这时整个数组都被排序。

下面是您代码的基本步骤:

  1. 设置初始间隔为数组长度的一半。
  2. 对间隔内的所有元素进行插入排序。
  3. 将间隔减半并重复上述过程。
  4. 当间隔减少到1时,整个数组都被排序。

您的实现基于希尔排序的基本思想,使用了一个三层循环结构来实现。外部循环控制间隔的递减,中间循环遍历每个间隔组,而内部循环则是基于插入排序的元素交换。

这个实现采用的增量序列为最简单的“每次除以2”,但在实际应用中,有些其他的增量序列可能会导致更好的性能。不过这个实现已经很好地展示了希尔排序的基本思想。

思路:通过对序列分组然后分别对每组进行排序,再把拍好的序列进行嵌合,简单来说就是不断缩小所排元素的密度最终完成最序列的排序。

void shell_sort(std::vector<int>& nums)
	{
		int n = nums.size();
		for (int gap = n / 2; gap > 0; gap /= 2)
		{
			for (int i = gap; i < n; ++i)
			{
				for (int j = i; j >= gap && nums[j] < nums[j - gap]; j -= gap)
					std::swap(nums[j], nums[j - gap]);
			}
		}
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值