基础算法(一)

快速排序

通过一趟排序将待排记录分隔成独立的两部分,其中的一部分比关键字小,后面一部分比关键字大,然后再对这前后的两部分分别采用这种方式进行排序,通过递归的运算最终达到整个序列有序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void quick_sort(int q[], int l, int r)//l指向最左边,r指向最右边
{
if (l >= r) return;//当l和r跑到一起时,结束运行,即只有一个数据或没有数据,就不需要排序

int i = l - 1, j = r + 1, x = q[l + r >> 1];//因为下面的代码不管判断先进行一次挪位,所以i和j要往外一位,确保能扫到所有的数
while (i < j)//x为分割点,>>1相当于/2
{
do i ++ ; while (q[i] < x);//比x小的放左边
do j -- ; while (q[j] > x);//比x大的放右边
if (i < j) swap(q[i], q[j]);//此时i和j都停在不符合条件的地方,用swap把值互换
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);//用递归对左边和右边的数据进行多次分割
}

下面是完整代码实现

给定你一个长度为 $n$ 的整数数列。

请你使用快速排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 $n$。

第二行包含 $n$ 个整数(所有整数均在 $1∼10^9$ 范围内),表示整个数列。

输出格式

输出共一行,包含 $n$个整数,表示排好序的数列。

数据范围

$1 \leq n \leq 100000$

输入样例:

1
2
5
3 1 2 4 5

输出样例:

1
1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;
int n;
const int N = 1e6 + 10;
int q[N];
void quick_sort(int q[], int l,int r) {
if (l >= r) { return; }
int i = l - 1;
int j = r + 1;
int x = q[l];
while (i<j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) { swap(q[i], q[j]); }
}
quick_sort(q,l,j);
quick_sort(q, j + 1, r);
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> q[i];
}
quick_sort(q, 0, n - 1);
for (int i = 0; i < n; i++) {
cout << q[i]<<" ";
}
return 0;
}

归并排序

  1. 确定分界点$(l+r)>>1$
  2. 递归排序左边和右边
  3. 归并————把两个有序的数组合并成一个有序的数列,即合二为一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void merge_sort(int q[], int l, int r)//left和right
{
if (l >= r) return;//只有一个数据或没有数据,就不需要排序

int mid = l + r >> 1;//取中间, 确定分界点
merge_sort(q, l, mid);//递归,排序右边
merge_sort(q, mid + 1, r);//递归,排序左边

int k = 0, i = l, j = mid + 1;//k表示两个数组合并了几个数,i是指向左半边的起点,j是指向右半边的起点
while (i <= mid && j <= r)//i小于左半边的边界,j小于右半边的边界
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];//左半边最小值小于右半边最小值时,用一个临时数组存储左半边
else tmp[k ++ ] = q[j ++ ];//即把最小的存到临时数组

while (i <= mid) tmp[k ++ ] = q[i ++ ];//左半边没有循环完,就把左半边剩下的存到临时数组里
while (j <= r) tmp[k ++ ] = q[j ++ ];//同上

for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];//把临时数组里存放的数据还到需要排序的数组里
}

给定你一个长度为 $n$ 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 $n$。

第二行包含 $n$ 个整数(所有整数均在 $1∼10^9$ 范围内),表示整个数列。

输出格式

输出共一行,包含 $n$ 个整数,表示排好序的数列。

数据范围

$1 \leq n \leq 100000$

输入样例:

1
2
5
3 1 2 4 5

输出样例:

1
1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

using namespace std;

const int N = 1000010;

int n;

int q[N],tmp[N];

void merge_sort(int q[], int l, int r)//left和right
{
if (l >= r) return;//只有一个数据或没有数据,就不需要排序

int mid = l + r >> 1;//取中间
merge_sort(q, l, mid);//递归,排序右边
merge_sort(q, mid + 1, r);//递归,排序左边

int k = 0, i = l, j = mid + 1;//k表示两个数组合并了几个数,i是指向左半边的起点,j是指向右半边的起点
while (i <= mid && j <= r)//i小于左半边的边界,j小于右半边的边界
if (q[i] <= q[j]) tmp[k++] = q[i++];//左半边最小值小于右半边最小值时,用一个临时数组存储左半边
else tmp[k++] = q[j++];//即把最小的存到临时数组

while (i <= mid) tmp[k++] = q[i++];//左半边没有循环完,就把左半边剩下的存到临时数组里
while (j <= r) tmp[k++] = q[j++];//同上

for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];//把临时数组里存放的数据还到需要排序的数组里
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> q[i];
}
merge_sort(q, 0, n - 1);
for (int i = 0; i < n; i++) {
cout << q[i] << ' ';
}
return 0;
}

二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}

给定一个按照升序排列的长度为 $n$ 的整数数组,以及 $q$ 个查询。

对于每个查询,返回一个元素 $k$ 的起始位置和终止位置(位置从 $0$ 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数$n$ 和$ q$,表示数组长度和询问个数。

第二行包含 $n$ 个整数(均在 $1∼10000$ 范围内),表示完整数组。

接下来 $q$ 行,每行包含一个整数 $k$,表示一个询问元素。

输出格式

共 $q$ 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

$1 \leq n \leq 100000$
$1 \leq q \leq 10000$
$1\leq k \leq 10000$

输入样例:

1
2
3
4
5
6 3
1 2 2 3 3 4
3
4
5

输出样例:

1
2
3
3 4
5 5
-1 -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;
const int N=100010;
int n, m;//n是数组长度,m是询问个数
int q[N];

int main()
{
cin >> n >> m;//n是数组长度,m是询问个数
for (int i = 0; i < n; i++)
{
cin >> q[i];
}
while (m--)//一共有m个询问
{
int x;
cin >> x;//x是要查询的数字
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r >> 1;
if (q[mid] >= x)r = mid;//有序数组的中间值比找的数组值x大或者等于,说明x在l和mid之间,即[l.mid],因此把右边界改为mid
else l = mid + 1;//有序数组的中间值比找的数组值x小,取不到mid,所以在[mid+1,r]中,因此把左边界改为mid+1
}
if (q[l] != x)cout << "-1 -1" << endl;//检查所查找的数据x是否存在于数组中,不存在就输出-1 -1
else
{
cout << l << ' ';

int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;//当l=r-1,即l和r仅仅相差一位,如果不+1,mid会等于l
if (q[mid] <= x)l = mid;//这里就会变成l=mid=l,陷入死循环
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}