基础排序
冒泡排序
每一轮都是从头开始,大的去后面,时间复杂度O(N^2)
//冒泡排序:每一轮都从头开始,两两比较,大的去后面,时间复杂度O(N^2)
void bubbleSort0(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
for (int i = 0; i < len; ++i)
{
for (int j = 0; j < len - i - 1; ++j)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
}
}
}
return;
}
//冒泡优化1:若某轮排序未发生交换,则已经有序,可直接终止
void bubbleSort1(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
bool flag;
for (int i = 0; i < len; ++i)
{
flag = false;//每轮排序开始时,将标志位置0
for (int j = 0; j < len - i - 1; ++j)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
flag = true;//只要有交换,就把标志位置1
}
}
if (flag==0)
return;
}
return;
}
//冒泡优化2:记录最后一次交换lastExchange的发生位置,则只有arr[0]—arr[lastExchange-1]是无序的
void bubbleSort2(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
bool flag;
int lastExchange = len - 1, pos = 0;
for (int i = 0; i < len - 1; ++i)
{
flag = 0;
for (int j = 0; j < lastExchange; ++j)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
flag = true;
pos = j;//需要使用一个中间变量,否则内层for循环的终止条件会变化
}
}
lastExchange = pos;
if (flag == 0)
return;
}
return;
}
选择排序
每一轮找到最小的元素放在当前序列的最前面,时间复杂度O(N^2)
//选择排序:每一轮都找到最小的元素放在当前序列的最前面,时间复杂度O(N^2)
void selectSort0(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
int index;
for (int i = 0; i < len; ++i)
{
index = i;
for (int j = i + 1; j < len; ++j)
{
if (arr[j] < arr[index])
{
index = j;
}
}
if (index != i)//index不是最初的i,即其余数字中有更小者
{
int tmp = arr[i];
arr[i] = arr[index];
arr[index] = tmp;
}
}
return;
}
//选择排序优化1:同时一轮遍历同时找到最大值和最小值
void selectSort1(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
int left = 0, right = len - 1;
int min, max;
while (left < right)
{
min = left, max = right;
for (int i = left; i <= right; ++i)
{
if (arr[i] < arr[min])
min = i;
if (arr[i] > arr[max])
max = i;
}
if (max != right)
swap(arr[max], arr[right]);
//若最小值位置min=本轮最右侧位置right,上面一步最大值交换后max=right,所以应把max赋给min;
//即便上面一步没有执行,即max=right,那么将max赋给min也是等价的
if (min == right)
min = max;
if (min != left)
swap(arr[min], arr[left]);
left++;
right--;
}
return;
}
插入排序
在一个数组中,将第一个元素看作有序元素序列里的唯一元素,然后依次插入后面的元素,时间复杂度O(N^2)
//插入排序:将一个记录插入到已经排好序的有序表中
void insertSort0(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
for (int i = 1; i < len; ++i)
{
int j = i-1;
int tar = arr[i];
while (j > -1 && arr[j] > tar)//找插入位置的同时,大的元素往后挪
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = tar;
}
return;
}
//插入排序优化1:使用二分查找在已有序部分找插入位置
void insertSort1(int *arr, int len)
{
if (arr == nullptr || len < 2)
return;
int left, right;
for (int i = 1; i < len; ++i)
{
int tar = arr[i];
int insertIndex;
left = 0, right = i - 1;
while (left <= right)//二分查找插入位置insertIndex
{
insertIndex = left + (right - left) / 2;
if (arr[insertIndex] > tar)
{
right = insertIndex - 1;
}
else
{
left = insertIndex + 1;
}
}
//跳出while的状态是right在left左侧一个位置,
//而下面for循环将从left到i-1挪到了left+1到i,所以空出的left就是要插入的位置
for (int j = i-1; j >= left; --j)
arr[j + 1] = arr[j];
arr[left] = tar;
}
return;
}
希尔排序
缩小增量排序,一趟一增量interval,用增量来分组,组内执行插入排序,时间复杂度O()
//希尔排序:缩小增量排序,一趟一增量interval,用增量来分组,组内执行插入排序
void shellSort0(int *arr, int len)
{
if (arr == NULL || len < 2)
return;
for (int interval = len / 2; interval > 0; interval /= 2)
{
for (int i = interval; i < len; ++i)
{
int tar = arr[i];
int j = i - interval;//当前分组中的前一个元素
while (j > -1 && arr[j] > tar)
{
arr[j + interval] = arr[j];
j -= interval;
}
arr[j + interval] = tar;
}
}
return;
}
分治法
1.拆分为子问题
2.递归求解子问题
3.合并子问题的解
注:partition和堆都能解决顺序统计量问题,堆更适合海量数据流
快速排序(关键在如何划分)
1.选主元,左侧都比主元小,右侧都比主元大(要保证最后一步与主元交换的值比主元小)
2.递归地对左右两个子序列排序
3.合并(快排是原址排序,不需合并)
//快速排序:
//双指针单向扫描
int quickPartition0(int *arr, int left, int right)
{
int privot = arr[left];
int pScan = left + 1, pRight = right;
while (pScan<=pRight)
{
if (arr[pScan] > privot)//当前扫描值<主元,扫描值与右侧指针值交换,右侧指针左移
{
swap(arr[pRight], arr[pScan]);
pRight--;
}
else
{
pScan++;
}
}
swap(arr[left], arr[pRight]);//需要保证与privot交换的值比privot小,所以是pRight
return pRight;
}
//双指针双向扫描
int quickPartition1(int *arr, int left, int right)
{
//三点中值法优化,在left,mid,right中选择一个中值作为主元,尽可能避免“一边倒”的情况
/*int mid = left + (right - left) / 2;
if (arr[left] >= arr[right] && arr[left] <= arr[mid])
mid = left;
else if (arr[right] >= arr[left] && arr[right] <= arr[mid])
mid = right;
else
mid = mid;
swap(arr[left], arr[mid]);*/
int privot = arr[left];
int pScan = left + 1, pRight = right;
while (pScan <= pRight)
{
//下面两个while中与主元的比较需要有一个带=
//否则若遇到有几个值相等的情况,会死循环(下面两个while都进不去,两个指针大小无变化)
while (pScan <= pRight && arr[pScan] <= privot)//从左往右扫描到第一个比主元大的数
{
pScan++;
}
while (pScan <= pRight && arr[pRight] > privot)//从右往左扫描到第一个比主元小的数
{
pRight--;
}
if (pScan < pRight)//如果大的在左,小的在右,就交换
{
swap(arr[pScan], arr[pRight]);
}
}
swap(arr[left], arr[pRight]);
return pRight;
}
void quickSort0(int *arr, int left,int right)
{
if (arr == nullptr)
return;
if (left < right)
{
//int mainPos = quickPartition0(arr, left, right);
int mainPos = quickPartition1(arr, left, right);
quickSort0(arr, left, mainPos - 1); //左半子序列做快排
quickSort0(arr, mainPos + 1, right); //右半子序列做快排
}
return;
}
//快排三指针分区扫描(了解即可)
void quickSort_3p(int arr[], int begin, int end)
{
int privot = arr[begin];
int scan = begin + 1;
int equal = begin + 1;
int big = end;
if (scan > big)
return;
while (scan <= big)
{
if (arr[scan] < privot)
{
swap(arr, scan, equal);
equal++;
scan++;
}
else if (arr[scan] > privot)
{
swap(arr, big, scan);
big--;
}
else
{
scan++;
}
}
swap(arr, begin, equal - 1);
partition_3p(arr, begin, equal - 2);
partition_3p(arr, big + 1, end);
}
归并排序(关键在合并)
1.将n个元素分成各n/2的两个子序列
2.递归地对两个左右子序列排序
3.合并两个子序列(辅助空间拷贝到原序列)
//归并排序(二叉树后序遍历)
int mergeHelper[10];
void merge0(int *arr, int left, int mid, int right)
{
for (int i = left; i <= right; ++i)
mergeHelper[i] = arr[i];
int leftBegin = left, rightBegin = mid + 1;
int index = left;
while (leftBegin <= mid && rightBegin <= right)
{
if (mergeHelper[leftBegin] <= mergeHelper[rightBegin])//左半子序列中当前值<=右半子序列当前值
{
arr[index++] = mergeHelper[leftBegin++];
}
else//左半子序列中当前值>右半子序列当前值
{
arr[index++] = mergeHelper[rightBegin++];
}
}
while (leftBegin <= mid)//若左半子序列没过完,继续拷贝
{
arr[index++] = mergeHelper[leftBegin++];
}
//若右半子序列没过完,不用理会,因为mergeHelper是从arr拷贝过来的,右半子序列相同
return;
}
void mergeSort0(int *arr, int left, int right)
{
if (arr == nullptr)
return;
if (left < right)
{
int mid = left + (right - left) / 2;
mergeSort0(arr, left, mid); //左半边子序列归并排序
mergeSort0(arr, mid + 1, right); //右半边子序列归并排序
merge0(arr, left, mid, right); //合并
}
return;
}
堆排序
1.堆化,反向调整使每个子树都是大顶堆或者小顶堆
2.把堆顶0号元素与最后一个元素对调;缩小堆的范围,对堆顶元素进行向下调整
//堆排序
//堆化,反向调整使每个子树都是大顶堆或者小顶堆
void minHeap(int arr[],int len)
{
//从最后一个非叶子节点开始“下沉”
for (int i = len / 2 - 1; i >= 0; i--)
{
minHeapFixDown(arr, i, len);
}
return;
}
void minHeapFixDown(int arr[], int i, int len)
{
//找到左右孩子
int min,temp;
int left = 2 * i + 1;
int right = 2 * i + 2;
//找到两者中较小的下标
if (left >= len)
{
return;
}
if (right >= len)
{
min = left;
}
else
{
if (arr[left] > arr[right])
min = right;
else
min = left;
}
//如果arr[i]比两个孩子都小,不调整;否则与两个孩子中较小的做交换
if (arr[i] <=arr[min])
{
return;
}
else
{
swap(arr, i, min);
}
//较小的孩子位置的值发生变化,i变更为较小孩子的位置,递归调整
minHeapFixDown(arr, min, len);
}
//堆排序
void heapSort(int arr[],int len)
{
//先对arr进行堆化
minHeap(arr, len);
for (int j = 0; j < len; j++)
{
cout << arr[j] << endl << endl;
}
for (int i = len - 1; i > 0; i--)
{
//把堆顶0号元素与最后一个元素对调
swap(arr, i, 0);
//缩小堆的范围,对堆顶元素进行向下调整
minHeapFixDown(arr, 0, i);
}
return;
}
桶排序
//每个桶中的插入
void insert(list<int>& bucket, int value)
{
auto iter = bucket.begin();
while (iter != bucket.end() && value >= *iter)
{
iter++;
}
bucket.insert(iter, value);
return;
}
void bucketSort(vector<int>& arr)
{
//数组长度不合格,return
int len = arr.size();
if (len <= 1)
{
return;
}
//找出最大最小值
int min = arr[0], max = arr[0];
for (auto value : arr)
{
if (min > value)
{
min = value;
}
if (max < value)
{
max = value;
}
}
//确定桶的数量,定义桶
int k = 10;//每个桶中的最大容量
int bucketNum = (max - min) / k + 1;
vector<list<int>> buckets(bucketNum);//把list放入vector中,每一个bucket都是一个list<int>
//遍历,入桶
for (auto value : arr)
{
int index = (value - min) / k;
insert(buckets[index], value);
}
//将排序结果修改到原数组
int index = 0;
for (int i = 0; i < bucketNum; i++)
{
if (buckets[i].size())
{
for (auto value : buckets[i])
{
arr[index++] = value;
}
}
}
//遍历出桶
/*for (auto value : arr)
{
cout << value << endl;
}*/
return;
}
计数排序
用辅助数组对数组中出现的数字计数,元素转下标,再下标转元素。若有负数,以下代码采用的方式是将负数放在正数之后
void countSort(int arr[],int len)
{
//找出最大值和最小值
int temp_min = 0;
int temp_max = arr[0];
for (int i = 0; i < len; i++)
{
if (arr[i] > temp_max)
temp_max = arr[i];
if (arr[i] < temp_min)
temp_min = arr[i];
}
//开辟辅助空间
int size = temp_max + 1 + abs(temp_min);
int *helper = new int[size];
memset(helper, 0, sizeof(int)*size);
for (int i = 0; i < len; i++)
{
if (arr[i] >= 0)
helper[arr[i]]++;
else
helper[abs(arr[i]) + temp_max]++;
}
int index = 0;
//先将负数拷贝到原数组
if (temp_min < 0)
{
for (int i = size - 1; i > temp_max; i--)
{
while (helper[i] != 0)
{
arr[index] = temp_max - i;
index++;
helper[i]--;
}
}
}
//再将正数拷贝到原数组
for (int i = 0; i < temp_max + 1; i++)
{
while (helper[i] != 0)
{
arr[index] = i;
index++;
helper[i]--;
}
}
delete[] helper;
return;
}
基数排序
首先按低位有效数字进行排序,逐次向上一位排序,直到最高位。传送门解释地肥肠到位!
void radixSort(int arr[], int len)
{
int MaxIndex = findMax(arr, len); //得到最大数的位数
int *buckets = new int[10]; //定义桶,创建10个是因为桶的编号对应当前位数0-9
int *tmp = new int[len];
int i, j, radix = 1;
int temp;
for (i = 0; i < MaxIndex; i++) //进行位数次数的排序
{
// 给桶赋初值
for (j = 0; j < 10; j++)
{
buckets[j] = 0;
}
// 统计每个桶里的元素出现的次数
for (j = 0; j < len; j++)
{
temp = (arr[j] / radix) % 10;
/* ( A[j] / radix ) %10的作用
例如第一个数是13,则 temp=(13/1)%10 == 3
第四个数是100,
第一轮循环radix = 1时 temp = (100/1)%10 == 0
第二轮循环radix = 10时 temp = (100/10)%10 == 0
第三轮循环radix = 100时 temp = (100/100)%10 == 1
就此依次从低位到高位得到值
*/
buckets[temp]++;
}
// 更改buckets[i],目的:让更改后的buckets[i]的值,是在数据在tmp[]中的位置
for (j = 1; j < 10; j++)
{
buckets[j] += buckets[j - 1];
/* 例如
buckets[0]=3
buckets[1]=2
buckets[2]=1
那么经过这个循环之后
buckets[0]=3
buckets[1]=5
buckets[2]=3
*/
}
// 将桶内记录依次放到tmp数组中
for (j = len - 1; j >= 0; j--)
{
temp = (arr[j] / radix) % 10; //和上面作用相同,获得每个元素当前位数的值
tmp[buckets[temp] - 1] = arr[j];
buckets[temp]--;
}
// 将临时数组中的内容复制到原数组中,倒序是为了不打乱之前几轮的已经排好的顺序
for (j = 0; j < len; j++)
{
arr[j] = tmp[j];
}
radix = radix * 10; // radix 的目的是保值下一次取到更高位的值
}
//删除数组
delete[]tmp;
delete[]buckets;
}
// 找到数据里最大的位数
int findMax(int arr[], int len)
{
int MaxNum = arr[0];
for (int i = 1; i < len; i++)
{
if (arr[i] > MaxNum)
MaxNum = arr[i];
}
// 此时已找到最大数MaxNum
// 接下来找到最大数的位数
int MaxIndex = 1; //初始值设置为1
while (MaxNum >= 10) //如果最大值不超过10,则MaxIndex就是1
{
MaxNum /= 10;
MaxIndex++;
}
return MaxIndex;
}