算法排序笔记

算法是什么

计算=信息处理
借助某种工具,遵照一定规则,以明确而机械的形式进行

计算模型=计算机=信息处理工具

所谓算法,即特定计算模型下,旨在解决特定问题的指令序列

  • 输入 待处理的信息(问题)
  • 输出 经处理的信息(答案)
  • 正确性 的确可以解决指定的问题
  • 确定性 任一算法都可以描述为一个由基本操作组成的序列
  • 可行性 每一基本操作都可实现,且在常数时间内完成
  • 有穷性 对于任何输入,经有穷次基本操作,都可以得到输出

程序等于算法加数据结构

两个主要方面

  • 时间复杂度
  • 空间复杂度

O、Θ、Ω、o、ω区别

  • Θ同时定义了上界和下界,f(n)位于上界和下界之间,且包含等号。

对于f(n),存在正数n0、c1、c2,使得当 n>=n0 时,始终存在 0 <= c1g(n) <= f(n) <= c2g(n),则我们可以用 f(n)=Θ(g(n))表示。

  • O定义了算法的上界。

对于f(n),存在正数n0、c,使得当 n>=n0 时,始终存在 0 <= f(n) <= c*g(n),则我们可以用 f(n)=O(g(n))表示。

  • o定义的也是算法的上界,不过它不包含等于,是一种不精确的上界,或者称作松上界(某些书籍翻译为非紧上界)。

对于f(n),存在正数n0、c,使得当 n>n0 时,始终存在 0 <= f(n) < c*g(n),则我们可以用 f(n)=o(g(n))表示。

  • Ω定义了算法的下界,与O正好相反。

对于f(n),存在正数n0、c,使得当 n>n0 时,始终存在 0 <= f(n) < c*g(n),则我们可以用 f(n)=o(g(n))表示。

  • ω同样定义的是下界,只不过不包含等于,是一种不精确的下界,或者称作松下界(某些书籍翻译为非紧下界)。

对于f(n),存在正数n0、c,使得当 n>n0 时,始终存在 0 <= c*g(n) < f(n),则我们可以用 f(n)=ω(g(n))表示。

排序算法

快排(Quick Sort)

void quick_sort(vector<int> &nums, int l, int r) {
	if (l + 1 >= r) {
		return;
	}
	int first = l, last = r - 1, key = nums[first];
	while (first < last){
		while(first < last && nums[last] >= key) {
			--last;
		}
		nums[first] = nums[last];
		while (first < last && nums[first] <= key) {
			++first;
		}
		nums[last] = nums[first];
	}
	nums[first] = key;//把第一个数调到最终位置上
	quick_sort(nums, l, first);
	quick_sort(nums, first + 1, r);
}

插入排序(Insertion Sort)

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]);//每次把后面未排序的序列中第一个移到前面已排序的序列中
		}
	}
}

冒泡排序(Bubble Sort)

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

选择排序(Selection Sort)

void selection_sort(vector<int> &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]);//每次从后面选最小与前面交换
	}
}

快排在递归进行部分的排序的时候,只会访问局部的数据,因此缓存能够更大概率的命中;而堆排序的建堆过程是整个数组各个位置都访问到的,后面则是所有未排序数据各个位置都可能访问到的,所以不利于缓存发挥作用。简答的说就是快排的存取模型的局部性(locality)更强,堆排序差一些。

在这里插入图片描述

贪心算法

顾名思义,贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。
举一个最简单的例子:小明和小王喜欢吃苹果,小明可以吃五个,小王可以吃三个。已知苹果园里有吃不完的苹果,求小明和小王一共最多吃多少个苹果。在这个例子中,我们可以选用的贪心策略为,每个人吃自己能吃的最多数量的苹果,这在每个人身上都是局部最优的。又因为全局结果是局部结果的简单求和,且局部结果互不相干,因此局部最优的策略也同样是全局最优的策略。

分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

def findContentChildren(self, g: List[int], s: List[int]) -> int:
    g.sort()
    s.sort()
    i=j=m=0
    while i<len(g) and j<len(s):       
        if s[j]>=g[i]:
            m+=1
            i+=1
        j+=1
    return m
int findContentChildren(vector<int>& children, vector<int>& cookies) {
	sort(children.begin(), children.end());
	sort(cookies.begin(), cookies.end());
	int child = 0, cookie = 0;
	while (child < children.size() && cookie < cookies.size()) {
		if (children[child] <= cookies[cookie]) ++child;
			++cookie;
	}
	return child;
}

分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

def candy(self, ratings: List[int]) -> int:
    l=len(ratings)
    a=[1 for i in range(l)]
    for i in range(1,l):
        if ratings[i-1]<ratings[i]:
            a[i]=a[i-1]+1
    for i in range(l-1,0,-1):
        if ratings[i]<ratings[i-1]:
            a[i-1]=max(a[i-1],a[i]+1)
    return sum(a)
int candy(vector<int>& ratings) {
	int size = ratings.size();
		if (size < 2) {
			return size;
		}
	vector<int> num(size, 1);
	for (int i = 1; i < size; ++i) {
		if (ratings[i] > ratings[i-1]) {
			num[i] = num[i-1] + 1;
		}
	}
	for (int i = size - 1; i > 0; --i) {
		if (ratings[i] < ratings[i-1]) {
			num[i-1] = max(num[i-1], num[i] + 1);
		}
	}
	return accumulate(num.begin(), num.end(), 0);
}

把所有孩子的糖果数初始化为 1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1。

无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

示例 1:

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
    a=sorted(intervals, key=lambda i: i[1])
    b,s=a[0][1],0
    for i in a[1:]:
        if b>i[0]:
            s+=1
        else:
            b=i[1]
    return s
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
	if (intervals.empty()) {
		return 0;
	}
	int n = intervals.size();
	sort(intervals.begin(), intervals.end(), [](vector<int> a, vector<int> b) {
		return a[1] < b[1];
	});
	int total = 0, prev = intervals[0][1];
	for (int i = 1; i < n; ++i) {
		if (intervals[i][0] < prev) {
			++total;
		} else {
			prev = intervals[i][1];
		}
	}
	return total;
}

在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值