算法学习第一周(排序算法)

-冒泡排序

1.其原理为:

  • 内循环: 使用相邻双指针 j , j + 1
    从左至右遍历,依次比较相邻元素大小,若左元素大于右元素则将它们交换;遍历完成时,最大元素会被交换至数组最右边 。
  • 外循环: 不断重复「内循环」,每轮将当前最大元素交换至 剩余未排序数组最右边 ,直至所有元素都被交换至正确位置时结束。
    2.复杂度分析:
  • 时间复杂度为 O(N^2) ,因为其是通过不断 交换元素 实现排序(交换 2 个元素需要 3 次赋值操作),因此速度较慢;
  • 空间复杂度 O(1): 只需原地交换元素,使用常数大小的额外空间。
void bubbleSort(vector<int> &nums) {
	int N = nums.size();
	for (int i = 0; i < N - 1; i++) {          // 外循环
		for (int j = 0; j < N - i - 1; j++) {  // 内循环
			if (nums[j] > nums[j + 1]) {
                // 交换 nums[j], nums[j + 1]
				swap(nums[j], nums[j + 1]);
			}
		}
	}
}

快速排序

1.其原理为:

  • 哨兵划分:以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。
  • 递归:对 左子数组 和 右子数组 分别递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。

2.复杂度分析:

  • 时间复杂度为 :

  • 最佳情况下: 每轮哨兵划分操作将数组划分为等长度的两个子数组;哨兵划分操作为线性时间复杂度 O(N);递归轮数共 O(log N) 。

  • 平均:对于随机输入数组,哨兵划分操作的递归轮数也为 O(log N)。

  • 最差: 对于某些特殊输入数组,每轮哨兵划分操作都将长度为 N 的数组划分为长度为 11和 N - 1 的两个子数组,此时递归轮数达到 N 。

  • 空间复杂度 O(N) : 快速排序的递归深度最好与平均皆为 logN ;输入数组完全倒序下,达到最差递归深度 N 。

int partition(vector<int>& nums, int l, int r) {
    // 以 nums[l] 作为基准数
    int i = l, j = r;
    while (i < j) {
        while (i < j && nums[j] >= nums[l]) j--;
        while (i < j && nums[i] <= nums[l]) i++;
        swap(nums[i], nums[j]);
    }
    swap(nums[i], nums[l]);
    return i;
}

void quickSort(vector<int>& nums, int l, int r) {
    // 子数组长度为 1 时终止递归
    if (l >= r) return;
    // 哨兵划分操作
    int i = partition(nums, l, r);
    // 递归左(右)子数组执行哨兵划分
    quickSort(nums, l, i - 1);
    quickSort(nums, i + 1, r);
}

// 调用
vector<int> nums = { 4, 1, 3, 2, 5, 1 };
quickSort(nums, 0, nums.size() - 1);

归并排序

1.其原理为:

  1. 将一个序列从中间位置分成两个序列;
  2. 在将这两个子序列按照第一步继续二分下去;
  3. 直到所有子序列的长度都为1,也就是不可以再二分截止。这时候再两两合并成一个有序序列。

2复杂度分析:

  • 时间复杂度为 :最佳 :NlogN,平均:NlogN,最差 :NlogN。
  • 空间复杂度 O(N)
void mergeSort(vector<int>& nums, int l, int r) {
    // 终止条件
    if (l >= r) return;
    // 递归划分
    int m = (l + r) / 2;
    mergeSort(nums, l, m);
    mergeSort(nums, m + 1, r);
    // 合并阶段
    int tmp[r - l + 1];             // 暂存需合并区间元素
    for (int k = l; k <= r; k++)
        tmp[k - l] = nums[k];
    int i = 0, j = m - l + 1;       // 两指针分别指向左/右子数组的首个元素
    for (int k = l; k <= r; k++) {  // 遍历合并左/右子数组
        if (i == m - l + 1)
            nums[k] = tmp[j++];
        else if (j == r - l + 1 || tmp[i] <= tmp[j])
            nums[k] = tmp[i++];
        else {
            nums[k] = tmp[j++];
        }
    }
}

// 调用
vector<int> nums = { 4, 1, 3, 2, 5, 1 };
mergeSort(nums, 0, nums.size() - 1);

请添加图片描述

#include<stdio.h>
int main()
{
	int a[200]={0},i,n,j,t;
	scanf("%d",&n);
	for(i=0;i<n;i++){
		scanf("%d",a+i);
	}	
	for(i=0;i<n-1;i++)
		for(j=0;j<n-i-1;j++)
			if(a[j]<a[j+1])
			{
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
			}
			for(i=0;i<10;i++)
				printf("%d ",a[i]);
				printf("\n");
			return 0;
}

2.最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例1

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例2

输入:arr = [0,1,2,1], k = 1
输出:[0]

解题思路
题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数 和 其他数字 两部分即可

根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k+1 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 k 个数 。

根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 kk ,若 true 则直接返回此时数组的前 k 个数字即可。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if (k >= arr.size()) return arr;
        return quickSort(arr, k, 0, arr.size() - 1);
    }
private:
    vector<int> quickSort(vector<int>& arr, int k, int l, int r) {
        int i = l, j = r;
        while (i < j) {
            while (i < j && arr[j] >= arr[l]) j--;
            while (i < j && arr[i] <= arr[l]) i++;
            swap(arr[i], arr[j]);
        }
        swap(arr[i], arr[l]);
        if (i > k) return quickSort(arr, k, l, i - 1);
        if (i < k) return quickSort(arr, k, i + 1, r);
        vector<int> res;
        res.assign(arr.begin(), arr.begin() + k);
        return res;
    }
};

3.数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5
解题思路
建立一个 小顶堆 A 和 大顶堆 B ,各保存列表的一半元素,且规定:
A 保存 较大 的一半,长度为 N/2,( N为偶数)或 (N+1)/2( N 为奇数);
B 保存 较小 的一半,长度为N/2( N为偶数)或 (N-1)/2( N 为奇数);
随后,中位数可仅根据 A,B 的堆顶元素计算得到.

class MedianFinder {
public:
    priority_queue<int, vector<int>, greater<int>> A; // 小顶堆,保存较大的一半
    priority_queue<int, vector<int>, less<int>> B; // 大顶堆,保存较小的一半
    MedianFinder() { }
    void addNum(int num) {
        if(A.size() != B.size()) {
            A.push(num);
            B.push(A.top());
            A.pop();
        } else {
            B.push(num);
            A.push(B.top());
            B.pop();
        }
    }
    double findMedian() {
        return A.size() != B.size() ? A.top() : (A.top() + B.top()) / 2.0;
    }
};

4. 把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

输入: [10,2]
输出: "102"
输入: [3,30,34,5,9]
输出: "3033459"

解题思路:
设数组 nums中任意两数字的字符串为 x 和 y ,则规定 排序判断规则 为:若拼接字符串 x + y > y + x ,则 x “大于” y ;反之,若 x + y < y + x,则 x “小于” y ;根据以上规则,套用任何排序方法对 nums 执行排序即可。

class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        string res;
        for(int i = 0; i < nums.size(); i++)
            strs.push_back(to_string(nums[i]));
        sort(strs.begin(), strs.end(), [](string& x, string& y){ return x + y < y + x; });
        for(int i = 0; i < strs.size(); i++)
            res.append(strs[i]);
        return res;
    }
};

5.扑克牌中的顺子
示例1

输入: [1,2,3,4,5]
输出: True

示例2

输入: [0,0,1,2,5]
输出: True

解题思路:
先对数组执行排序。
判别重复: 排序数组中的相同元素位置相邻,因此可通过遍历数组,判断 nums[i] = nums[i + 1]是否成立来判重。
获取最大 / 最小的牌: 排序后,数组末位元素nums[4] 为最大牌;元素 nums[joker] 为最小牌,其中 joker 为大小王的数量。

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        int joker = 0;
        sort(nums.begin(), nums.end()); // 数组排序
        for(int i = 0; i < 4; i++) {
            if(nums[i] == 0) joker++; // 统计大小王数量
            else if(nums[i] == nums[i + 1]) return false; // 若有重复,提前返回 false
        }
        return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值