排序和二分搜

排序

排序

  • 選擇排序
  • 泡沫排序
  • 插入排序

選擇排序

找最大值

泡沫排序

讓最大的浮上來

插入排序

找合適的位置

sort函式

#inlcude<algorithm>

預設從小到大

用法

sort(起始指標, 結尾指標)

不包含

起始指標

結尾指標

排序範圍

實際用法

有一個陣列a, 要排序a[0]~a[n - 1]

  • sort(&a[0], &a[n]);
  • sort(a, a + n);

如果要由大到小呢?

  • 寫一個回傳bool的比較函式
  • sort(起始指標, 結尾指標, 比較函式名稱)
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
bool cmp(int a, int b) {
	return a > b;
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n, cmp);
	for (int i = 0; i < n; i++) {
		cout << a[i] << ' ';
	}
	cout << '\n';
}

a915

排序完之後要幹嘛?

二分搜尋法

玩過猜數字嗎?

  • 每次猜一個數字
  • 如果猜到就結束遊戲
  • 如果沒猜到,會知道答案比你猜的數字大或小

有沒有一個策略可以讓你有在最差情況下,猜的次數最少?

每次猜數字範圍的中間

  • 猜到就結束
  • 如果答案比猜的小,可以刪掉所有比它大的
  • 如果答案比猜的大,可以刪掉所有比它小的

最慘會猜幾次?

  • 假設數字範圍有\(n\)個數字
  • 每次沒猜到都會刪掉一半
  • 設最壞情況下會猜k次
  • 在猜到第k-1次的時候,可能的範圍要多於一個
{\frac n {2^{k - 1}}} > 1
n > 2^{k - 1}
\log_{2}n > k - 1

假設要猜的範圍是\(0\)~\(2^{31} - 1\)

最多只需要猜31次!

用數學算算看

二分搜尋法

在一個有序陣列中快速找到一個值的演算法

每次找搜尋陣列範圍的中間位置

如果他比你的目標大,就往左找

如果他比你的目標小,就往右找

二分搜尋法

遞迴想法

設函式\(f(l, r, val)\)為整數\(val\)在陣列a[\(l\)~ \(r\)]區間中的位置

  • 如果\(l\) == \(r\),回傳\(l\)
  • 找陣列a[\(l\) ~ \(r\)]中間位置\(mid = (l + r) / 2\)
  • 如果a[\(mid\)]=val,回傳\(mid\)
  • 如果a[\(mid\)]>val,回傳\(f(l, mid - 1, val)\)
  • 如果a[\(mid\)]<val,回傳\(f(mid + 1, r, val)\)
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
int f(int l, int r, int val) {
	if (l == r) {
		if (a[l] == val)
			return l;
		else
			return -1;
	}
	int mid = (l + r) / 2;
	if (a[mid] == val)
		return mid;
	else if (a[mid] > val)
		return f(l, mid - 1, val);
	else
		return f(mid + 1, r, val);
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n);
	int q;
	cin >> q;
	cout << f(0, n - 1, q) << '\n';
}
#include<iostream>
#include<algorithm>
using namespace std;
int a[200005];
int f(int l, int r, int val) {
	while (l < r) {
		int mid = (l + r) >> 1;
		if (a[mid] == val)
			return mid;
		else if (a[mid] < val)
			l = mid + 1;
		else
			r = mid - 1;
	}
	if (a[l] == val)
		return l;
	return -1;
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	sort(a, a + n);
	int q;
	cin >> q;
	cout << f(0, n - 1, q) << '\n';
}

對答案二分搜

  • 答案在一個固定範圍內
  • 題目要找符合條件的最小值或最大值
  • 答案具單調性(必要)

對答案二分搜條件

單調性是啥?

zerojudge c575

假設現在的直徑是k

  • 如果直徑k可以成為答案
    • 那直徑比k大的都可以是答案
  • 如果直徑k不能是答案
    • 那直徑比k小的都不能是答案

假設現在的直徑是k

  • 如果直徑k可以成為答案
    • 那直徑比k大的都可以是答案
  • 如果直徑k不能是答案
    • 那直徑比k小的都不能是答案

如果直徑i可以,表示為1

如果不行,表示為0

用數列表示可能長這樣

0, 0, 0, 0, 1, 1, 1, 1, 1, 1...

二分搜第一個出現的1!

怎麼檢查答案?

  • 從左邊開始,找第一個沒被覆蓋的點
  • 加上一個基地台,可以覆蓋(現在位置+k)的所有點
  • 再找下一個沒被覆蓋的點
  • 反覆執行

複雜度

  • 檢查需要\(O(n)\)
  • 二分搜需要\(O(\log{c})\) c為座標範圍
  • 複雜度\(O(n\log{c})\)
#include <iostream>
#include <algorithm>
using namespace std;
int N, K;
int p[50005];
bool test(int d){
	int now, nowpoint = 0;
	for(int i = 0; i < K; i++){
		now = p[nowpoint];
		now+=d;
		while(p[nowpoint] <= now){
			nowpoint++;
			if(nowpoint >= N)
				return 1;
		} 
	}
	return 0;
}

int main()
{
	while(cin >> N >> K){
		for(int i = 0; i < N; i++){
			cin >> p[i];
		}
		sort(p, p+N);
		int l = 1, r = 1e9;
		while(l != r){
			int mid = (l + r) / 2;
			if(test(mid)){
				r = mid;
			}else{
				l = mid + 1;
			}
		}
		cout<<l<<endl;
	}
    return 0;
}

排序和二分搜

By scottchou

排序和二分搜

  • 302