分治思想:分而治之,把一个复杂的问题按一定的"分解方法",分为等价规模较小的若干部分,逐个解决问题,分别找出每个问题的解
基本步骤:
1.分解过程:把问题分解为两个或者多个问题
2.治理问题:分别解决子问题
3.合并过程:把所有子问题合并起来(递归、递推)
快速排序:分组排序 无限分组,无限把数据分成有序的两组,分到没办法再分组后,整个数组自然有序
10个
5个 5个 左边的都小于右边
2 3 2 3
1 1 1 2 1 1 1 2
1 1 1 1
分组过程中保证 左边每个元素比右边每个元素要小
归并排序:有序的两个数组合并成一个有序数组 一开始只负责拆,拆到只有一个元素后,自然就有序了,有序后,把有序的两个数组合并成一个有序数组
10个
5个 5个 左边的都小于右边
2 3 2 3
1 1 1 2 1 1 1 2
1 1 1 1
1 1 1 2 1 1 1 2
2 3 2 3
5 5
10
step1:两个有序数组合并成一个有序数组
step2:一个无序数组用归并排序排序成一个有序数组
二分查找:在有序序列(二叉树、数组、链表 . . .)中,二分查找的前提是有序,如果无序没办法分而治之
效率较高,每次可以排除掉一半
在 1 ~ 100 中找 1个数,先找 50
要找的数如果 > 50,取大的一半
要找的数如果 < 50,取小的一半
时间复杂度分析
比如:总共有n个元素,每次查找的区间大小就是n,n / 2,n / 4,…,n / 2^k(接下来操作元素的剩余个数),其中k就是循环的次数
由于 n / 2^k 取整后 >= 1,即令 n / 2^k = 1,
可得k = log2n,(是以2为底,n的对数),所以时间复杂度可以表示O() = O(logn)
部分内容参考:"二分查找"算法的时间复杂度
普通数组查找
#include<stdio.h>
int searchData(int array[],int arrayNum,int posData)
{
for(int i = 0;i < arrayNum;i++ )
{
if(array[i] == posData)
return i;
}
return -1;
}
循环方式实现二分查找
直接找下标为一半的位置 - - - 元素个数 / 2
注意:不能直接 len / 2,如果已经排除前面一半,需要去后面一半去找,不能直接用 len / 2
#include <iostream>
using namespace std;
#define NUM 18
void travel(int* a, int len, bool isAfter = false);
//冒泡排序
void bubble_sort(int* a, int len);
/*
从arr数组中找值为findData的元素
找到返回其下标 没找到返回-1
len为数组元素个数
*/
int halfFind(int* arr, int len, const int& findData);
int main(){
int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
33, 83, 27, 95, 2, 88 };
bubble_sort(arr, NUM);
travel(arr, NUM, true);
int n;
int ret;
while (1){
cout << "请输入要找的元素值:";
cin >> n;
ret = halfFind(arr, NUM, n);
if (-1 == ret){
cout << "没找到!" << endl;;
}
else{
cout << "找到了,数据:" << arr[ret] << "下标为:" << ret << endl;
}
}
while (1);
return 0;
}
void travel(int* a, int len, bool isAfter){
if (isAfter){
cout << "after sort:";
}
else{
cout << "before sort:";
}
for (int i = 0; i < len; i++)
cout << a[i] << " ";
cout << endl;
}
//冒泡排序
void bubble_sort(int* a, int len){
//n-1次
for (int i = 0; i < len - 1; i++){
//比较相邻两个,大的后挪
for (int j = 0; j < len - i - 1; j++){
if (a[j] > a[j + 1]){//比较相邻两个 不满足要求
//交换
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
int halfFind(int* arr, int len, const int& findData){
int m; //中间的下标
int left=0; //左边下标
int right=len-1; //右边下标
while (1){
//把中间下标算出来
m = left + (right - left) / 2;
if (findData == arr[m]){//判断是否找到了
return m;//找到了 返回
}
else{
//没找到 修改left和right的值 继续循环
if (findData > arr[m]){//要找数据比中间数据大
left = m + 1;//排除掉左边->右边下标保持不变,左边下标修改为m+1
}
else{//要找数据比中间数据小
right = m - 1;//排除掉右边->左边下标保持不变,右边下标修改为m-1
}
}
if (left > right) break;//找不到 防止死循环 在left==right之后 总会出现left>right的情况
}
return -1;
}
/*输出*/
after sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
请输入要找的元素值:2
找到了,数据:2下标为:0
请输入要找的元素值:97
找到了,数据:97下标为:17
请输入要找的元素值:1
没找到!
请输入要找的元素值:98
没找到!
请输入要找的元素值:42
没找到!
请输入要找的元素值:41
找到了,数据:41下标为:6
请输入要找的元素值:
递归方式实现二分查找
循环能实现的递归一定能实现,递归能实现的循环不一定能实现
循环改为递归:数据的改变 改成 参数的改变,结束循环 改成 return
在实现代码时,如果循环和递归都可以实现,一般认为循环更快,由于递归存在函数的调用过程
/*
从arr数组中找值为findData的元素
找到返回其下标 找不到返回-1
len为数组元素个数
*/
int half_find(int* arr, int len, const int& findData);
//用于递归的函数
int Half_Find(int* arr, int left, int right, const int& findData); //再加一个函数 在函数中递归 函数的参数 len 改为左边和右边
int half_find(int* arr, int len, const int& findData){
return Half_Find(arr, 0, len - 1, findData); //传入要查找的数组 左 右 要找的数据
}
int Half_Find(int* arr, int left, int right, const int& findData){
if (left > right) return -1; //找不到 防止死循环
int m = left + (right - left) / 2; //计算中间值
if (findData == arr[m]){//判断是否找到了
return m;//找到了 返回
}
//没找到 修改left和right的值 继续循环
if (findData > arr[m]){//要找数据比中间数据大
return Half_Find(arr, m + 1, right, findData); //排除掉左边->改变左边为m+1,右边不变
}
else{//要找数据比中间数据小
return Half_Find(arr, left, m - 1, findData); //排除掉右边->改变右边为m-1,左边不变
}
}
/*输出*/
after sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
请输入要找的元素值:2
找到了,数据:2下标为:0
请输入要找的元素值:97
找到了,数据:97下标为:17
请输入要找的元素值:1
没找到!
请输入要找的元素值:98
没找到!
请输入要找的元素值:42
没找到!
请输入要找的元素值:41
找到了,数据:41下标为:6
请输入要找的元素值:
两个有序数组合并成一个有序数组
引入一个临时数组,比较两个下标下的值,把小的放入临时数组中,右挪,再去比较. . .直到有一边挪完为止,再把另一边剩下的挪到临时数组中即可
#include <iostream>
using namespace std;
#define NUM 18
void travel(int* a, int len, bool isAfter = false);
/*
left ~ mid 左边的
mid+1 ~ right 右边的
*/
void MergeSort(int* a, int left, int mid, int right);
int main() {
#if 1
int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
33, 83, 27, 95, 2, 88 };
#else //先写两个有序的数组:前后两个数组都是有序的,中间未必有序-> 合并成一个数组后依然有序
int arr[NUM] = { 23, 26, 31, 41, 53, 58, 59, 93, 97,
2, 27, 33, 62, 64, 83, 84, 88, 95 };
#endif
travel(arr, NUM, true);
//MergeSort(arr, 0, 0 + (NUM - 1 - 0) / 2, NUM - 1);
//MergeSort(arr, 0, 8, 17);
//merge_sort(arr, 0, 17);
mergeSort(arr, NUM);
travel(arr, NUM, true);
while (1);
return 0;
}
void travel(int* a, int len, bool isAfter){
if (isAfter){
cout << "after sort:";
}
else{
cout << "before sort:";
}
for (int i = 0; i < len; i++)
cout << a[i] << " ";
cout << endl;
}
/*
left ~ mid 左边的
mid+1 ~ right 右边的
*/
void MergeSort(int* a, int left, int mid, int right){ //左边往中间挪,中间 + 1 挪到右边为止
int L = left; //绿色箭头
int R = mid + 1; //红色箭头
int k = 0; //作为pTemp的下标
//1 申请临时内存空间
int* pTemp = new int[right - left + 1];
//2 一边比较一边把数据从a放到pTemp中
while (L<=mid && R<=right){ //要考虑不能超过范围->只要有一个超过了,意味着有一边放完了-> 循环结束
if (a[L] < a[R]) pTemp[k++] = a[L++]; //如果左边的小,把左边的放入pTemp数组中
else pTemp[k++] = a[R++]; //如果右边的小,把右边的放入pTemp数组中
}
//3 把剩下的一半放入pTemp中-> 不知道是左边没放完还是右边没放完
#if 0
while (L <= mid){ pTemp[k++] = a[L++]; } //左边没放完,把左边的放完
while (R <= right){ pTemp[k++] = a[R++]; } //右边没放完,把右边的放完
#else
if (L <= mid) {
memcpy(pTemp + k, a + L, sizeof(int)*(mid-L+1));
}
else {
memcpy(pTemp + k, a + R, sizeof(int)*(right-R+1));
}
#endif
//4 pTemp中数据覆盖回 a
#if 0
int j = 0;
for (int i = left; i <= right; i++) {
a[i] = pTemp[j++];
}
#else
memcpy(a + left, pTemp, sizeof(int)*(right - left + 1)); //元素个数 == 最后一个位置的下标 - 第一个位置的下标 + 1
#endif
//5 释放
delete[] pTemp;
}
一个无序数组用归并排序排序成一个有序数组
//归并排序
void merge_sort(int* a, int left, int right);
//常规写法归并排序
void mergeSort(int* a, int len);
void merge_sort(int* a, int left, int right){
if (left < right){//如果左边==右边就不用拆了
int m = left + (right - left) / 2;
//拆
merge_sort(a, left, m); //拆左边
merge_sort(a, m + 1, right);//拆右边
//合
MergeSort(a, left, m, right);
//travel(a+left, right-left+1);
}
}
void mergeSort(int* a, int len){
merge_sort(a, 0, len - 1);
}
/*输出*/
after sort:31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 95 2 88
before sort:31 41
before sort:31 41 59
before sort:26 53
before sort:26 31 41 53 59
before sort:58 97
before sort:23 93
before sort:23 58 93 97
before sort:23 26 31 41 53 58 59 93 97
before sort:62 84
before sort:62 64 84
before sort:33 83
before sort:33 62 64 83 84
before sort:27 95
before sort:2 88
before sort:2 27 88 95
before sort:2 27 33 62 64 83 84 88 95
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
after sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
快速排序
快速排序:分组排序 无限分组,在分组的过程中,保证分成的两个组是有序的,左边的每一个元素都比右边的每一个元素要小
组分完后,整个数组就有序了
用两个指针实现左右两边有序,如果规定最左边的数据为临时数据,先挪 r,如果规定最右边的数据为临时数据,先挪 L:让两个指针按图中的方式移动,在移动的过程中调整位置,保证两个指针在同一个位置的时候,左边的每一个元素都比右边的每一个元素小
不采用交换而是采用覆盖的方式,可以提高效率
如果右指针对应的数字大于31,直接左挪. . .如果小于31,需要把当前值覆盖到左指针所在的位置
如果左指针对应的数字小于31,直接右挪. . .如果大于31,需要把当前值覆盖到右指针所在的位置
到达两个左右指针重合的情况,把 31覆盖回来即可
最后得到:保证了临时数据在中间,并且定位到了中间数据,因为左右指针重合了,比临时数据 31 小的在左边,比临时数据 31 大的在右边
继续针对2~26 和 58~88 把它们看成一个数组,继续进行以上操作,直到整个数组有序
时间复杂度分析:最容易理解最全的快排的最好时间复杂度分析_XIAXIAgo的博客-CSDN博客_快排的时间复杂度分析
#include <iostream>
using namespace std;
#define NUM 18
void travel(int* a, int len, bool isAfter = false);
//分组排序
void quick_sort(int* a, int left, int right);
int main(){
int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
33, 83, 27, 95, 2, 88 };
travel(arr, NUM, true);
quick_sort(arr, 0, 17);
travel(arr, NUM, true);
while (1);
return 0;
}
void travel(int* a, int len, bool isAfter){
if (isAfter){
cout << "after sort:";
}
else{
cout << "before sort:";
}
for (int i = 0; i < len; i++)
cout << a[i] << " ";
cout << endl;
}
//分组排序
void quick_sort(int* a, int left, int right){
if (left >= right) return;
//假设a[left]是中间数据
int temp = a[left];
int L = left; //用来移动的左下标
int R = right; //用来移动的右下标
//循环让L和R并拢
while (L < R){ //L==R的情况就并拢了
//先挪R
while (L < R && a[R] >= temp) R--; //可能在挪的过程中出现L > R的情况 大于的情况 R-- 即可
a[L] = a[R]; //停住后 要用右边的覆盖左边的
//再挪L
while (L < R && a[L] < temp) L++;
a[R] = a[L]; //出现大于或者等于的情况 要用左边的覆盖右边的
}
a[L] = temp; //覆盖完后 最后把temp覆盖回来
travel(a, NUM);
//继续拆 拆到不能拆为止
quick_sort(a, left, L - 1); //左边继续拆 左边还是left 右边变成L-1或者R-1 L == R
quick_sort(a, L + 1, right); //右边继续拆 L == R
}
/*输出*/
after sort:31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 95 2 88
before sort:2 27 23 26 31 58 97 93 53 84 62 64 33 83 59 95 41 88 //L和R的值都是4
before sort:2 27 23 26 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 26 23 27 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 23 26 27 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 23 26 27 31 41 33 53 58 84 62 64 93 83 59 95 97 88
before sort:2 23 26 27 31 33 41 53 58 84 62 64 93 83 59 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 97 95
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
after sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
普通方式找最大值和最小值
#include<stdio.h>
//数组 长度 最大值 最小值
void searchMaxMin(int array[],int arrayNum,int* maxIndex,int* minIndex)
{
//假设第一个元素是最大值也是最小值
int max = array[0];
int min = array[0];
//最大元素和最小元素的下标都等于0
*maxIndex = 0;
*minIndex = 0;
//从第二个开始比较
for(int i = 1;i < arrayNum;i++ )
{
//如果当前元素 > max需要改变最大值与最大值的下标
if(array[i] > max)
{
max = array[i];
*maxIndex = i;
}
//同理找最小值
if(array[i] < min)
{
min = array[i];
*minIndex = i;
}
}
}
int main()
{
int array[10] = {4,5,7,8,9,0,1,6,2,3};
int maxIndex = -1;
int minIndex = -1;
searchMaxMin(array,10,&maxIndex,&minIndex);
printf("max = %d\nmin = %d\n",maxIndex,minIndex);
return 0;
}
/*输出*/
max = 4
min = 5
分而治之的方式找最大值和最小值
每两个元素比较出最大值和最小值,把比较出的最大值、最小值和前一组的最大值、最小值去比较
会出现元素总个数为奇数的情况需要处理
void searchMaxMin2(int array[], int arrayNum, int* maxIndex, int* minIndex)
{
//1.只有一个元素既是最大值也是最小值
if (arrayNum == 1)
{
*maxIndex = *minIndex = 0;
}
//2.先解决奇偶问题-> 如果是奇数下一组比较从下标1开始 如果是偶数下一组比较从下标2开始
//奇数下一组从1开始
int n = 1;
//奇数
if (arrayNum % 2 == 1)
{
//只有一个元素的一组
*maxIndex = *minIndex = 0;
}
//偶数
else
{
//两个元素一组-> 直接把第0个元素和第1个元素拿出来比较找出最大值和最小值
if (array[0] > array[1])
{
*maxIndex = 0;
*minIndex = 1;
}
else
{
*maxIndex = 1;
*minIndex = 0;
}
//偶数下一组从2开始
n = 2;
}
//每次比较一组(两个)元素
for (int i = n; i < arrayNum; i += 2)
{
//一组的左边和右边比较出当前最大值和最小值
if (array[i] > array[i + 1]) //下一组找出最小的和最大的
{
if (array[i] > array[*maxIndex]) //把当前组的最大值和上一组的最大值比
{
*maxIndex = i; //如果比上一组的最大值大就改变最大值
}
if (array[i + 1] < array[*minIndex]) //把当前组的最小值和上一组的最小值比
{
*minIndex = i + 1; //同理改变最小值
}
}
else
{
if (array[i + 1] > array[*maxIndex])
{
*maxIndex = i + 1;
}
if (array[i] < array[*minIndex])
{
*minIndex = i;
}
}
}
}