下面是九大排序算法的总体分类:
接下来,对每个排序算法进行分析:
一、直接插入排序(straight insertion sort):
1.算法思想:将数据插入到已经排好的序列中。有序序列初始只有一个元素,将下一个的元素插入,有序队列增长, 直到最后整个要排序的队列都有序。
2.时间复杂度:
最好时间复杂度:O(N) :原有数据已经有序
最坏时间复杂度O(N^2):原有数据完全或接近逆序
3.应用场景:适用于元素已经基本有序,不适用于元素逆序或接近逆序的情况。
4.代码实现:
代码包含两个for循环, 第一个标记当前要插入的元素位置, 第二个为单趟插入,将要插入的元素插入到有序序列中。
//插入排序
void InsertSort(int* arr, size_t size)
{
assert(arr);
for(size_t j = 1; j < size; ++j)//要插入数据的位置
{
int insert = arr[j]; //要插入的数据
int i = j-1;
for( ; i >= 0 ;--i) //单趟插入
{
if(arr[i] > insert)
arr[i+1] = arr[i];
else
break;
}
arr[i+1] = insert;
}
}
二、希尔排序(Shell Sort),又名缩小增量排序:
当要排序的数据列完全或接近逆序时,用插入排序时间复杂度接近O(N^2),为了优化这种情况,减少插入时的数据移动,提出了希尔排序。
1.算法思想:将数据分组,对每组的数据进行插入排序,然后缩小分组的大小再进行插入排序,直到分组大小为1,则要排列的数据有序。(预排序+插入排序)
2.时间复杂度:
3.应用场景:适用于元素接近逆序, 不适用于元素基本有序的情况。
4.代码实现:
gap为每组的大小,当gap=1时,相当于插入排序,gap的最小值为1.
//希尔排序
void ShellSort(int* arr, size_t size)
{
assert(arr);
int gap = size;
while(gap != 1)
{
gap = gap/3 + 1;
for(size_t j = gap; j < size; ++j)
{
int insert = arr[j];
int i = j-gap;
for( ; i>=0; i-=gap)
{
if(arr[i] > insert)
arr[i+gap] = arr[i];
else
break;
}
arr[i+gap] = insert;
}
}
}
三、选择排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
void SelectSort(int* arr, size_t size)
{
assert(arr);
int left = 0;
int right = size-1;
while(left < right)
{
size_t max = left; //记录最大值的下标
size_t min = left;
for(int i = left; i <= right; ++i)
{
if(arr[i] > arr[max])
max = i;
if(arr[i] < arr[min])
min = i;
}
swap(arr[left], arr[min]);
//注意:防止上面的交换使max值不正确。
//当max的值为左时,交换会让max值改变,这里要调整。
if(left == max)
max = min;
swap(arr[right], arr[max]);
left++;
right--;
}
}
四、堆排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
void AdjustDown(int* arr, size_t size, size_t parent)
{
size_t child = parent*2+1;
while(child < size)
{
if(child+1<size && arr[child+1] > arr[child])
++child;
if(arr[parent] < arr[child])
swap(arr[parent], arr[child]);
else
break;
parent = child;
child = parent*2+1;
}
}
//堆排序
void HeapSort(int* arr, size_t size)
{
assert(arr);
//建堆
int parent = (size-1-1)/2;
while(parent >= 0)
{
AdjustDown(arr, size, parent);
parent--;
}
//排序
int index = size-1;//记录交换的位置
while(index>0)
{
swap(arr[0], arr[index]);
AdjustDown(arr, index, 0);
--index;
}
五、冒泡排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
void BubbleSort(int* arr, size_t size)
{
assert(arr);
for(size_t i = 0; i<size-1; ++i)
{
for(size_t j = 1; j < size-i; ++j)
{
if(arr[j-1] > arr[j])
{
swap(arr[j-1],arr[j]);
}
}
}
}
六、快速排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
//快速排序
//优化一:三数取中法
size_t GetMid(int* arr, size_t left, size_t right)
{
size_t mid = left+((right-left)>>2);
if(arr[left] < arr[mid])
{
if(arr[mid] < arr[right])
return mid;
else if(arr[left] < arr[right])
return right;
else
return left;
}
else //left大
{
if(arr[left] < arr[right])
return left;
else if(arr[right] < arr[mid])
return mid;
else
return right;
}
}
//法1:左右指针
size_t PartSort1(int* arr, size_t left, size_t right)
{
size_t key = right;
size_t mid = GetMid(arr, left, right);
if(key != mid)
swap(arr[key], arr[right]);
while(left < right)
{
//left找大
while(left < right && arr[left] <= arr[key])
++left;
while(left < right && arr[right] >= arr[key])
--right;
swap(arr[left], arr[right]);
}
if(right != key)
swap(arr[left], arr[key]);
return left;
}
//法2:挖坑法
size_t PartSort2(int* arr, size_t left, size_t right)
{
int key = arr[right];
while(left < right)
{
while(left < right && arr[left] <= key)
++left;
arr[right] = arr[left];
while(left < right && arr[right] >= key)
--right;
arr[left] = arr[right];
}
//right == left
arr[right] = key;
return right;
}
//法3:前后指针法
size_t PartSort3(int* arr, int left, int right)
{
int cur = left-1;
int prev = left-1;
int key = right;
while(cur < right)
{
while(cur < right && arr[++cur] >= arr[key]);//cur找小
++prev;
swap(arr[prev], arr[cur]);
}
return prev;
}
void QuickSort(int* arr, size_t left, size_t right)
{
assert(arr);
if(right-left > 0)
{
//优化二:区间数量较少时,用插入排序替代递归
if(right-left > 5)
{
int div = PartSort3(arr, left, right);
//[left, div-1] [div+1, right]
if(div != left) //注意:当div为边界时,边界的一边不需要继续排序
QuickSort(arr, left, div-1);
if(div != right)
QuickSort(arr, div+1, right);
}
else
{
InsertSort(&arr[left], right-left+1);
}
}
}
七、归并排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
//[left, right]
void MSort(int* arr,int* tmp, int left, int right)
{
if(right - left > 0)
{
int mid = left+(right-left)/2;
MSort(arr, tmp, left, mid);
MSort(arr, tmp, mid+1, right);
//合并
int begin1 = left, end1 = mid;
int begin2 = mid+1, end2 = right;
int i = left;
while(begin1<=end1 && begin2<= end2)
{
if(arr[begin1] < arr[begin2])
{
tmp[i++] = arr[begin1];
++begin1;
}
else
{
tmp[i++] = arr[begin2];
++begin2;
}
}
while(begin1 <= end1)
{
tmp[i++] = arr[begin1];
++begin1;
}
while(begin2 <= end2)
{
tmp[i++] = arr[begin2];
++begin2;
}
for(int j = left; j <= right; ++j)
{
arr[j] = tmp[j];
}
}
}
void MergeSort(int* arr, size_t size)
{
assert(arr);
int* tmp = new int[size];
MSort(arr, tmp, 0, size-1);
delete[] tmp;
}
八、计数排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
//1.计数排序:利用哈希的直接定址法,适用于数据比较集中的排序
void CountSort(int* arr, size_t size)
{
assert(arr);
int max = arr[0];
int min = arr[0];
for(int i = 1 ; i < size; ++i)
{
if(arr[i] > max)
max = arr[i];
if(arr[i] < min)
min = arr[i];
}
int range = max - min + 1;
int* tmp = new int[range];//存储每个数据出现的次数
memset(tmp, 0, sizeof(tmp[0])*range);
for(int i = 0; i < size; ++i)
{
tmp[arr[i] - min]++;
}
int j = 0;
for(int i = 0; i < range; ++i)
{
while(tmp[i]--)
{
arr[j++] = min + i;
}
}
delete[] tmp;
}
九、基数排序:
1.算法思想:
2.时间复杂度:
3.适用场景
4.代码实现:
//2.基数排序
// 最高位优先 -- MSD
// 最低位优先 -- LSD
//
void MSDSort(int* arr, size_t size)
{
assert(arr);
int max = arr[0];
for(int i = 1; i < size; ++i)
if(arr[i] > max)
max = arr[i];
//统计最大位数
int digit = 1; //最大位数
int div = 10;
while((max / div) > 0)
{
++digit;
div *= 10;
}
//从低位到高位排序
int* beginIndex = new int[10];//记录每个桶的开始下标
int* tmp = new int[size];
div = 1;
while(digit--)
{
//统计每个桶的开始下标
memset(beginIndex, 0, sizeof(beginIndex[0])*10);
for(int i = 0; i < size; ++i)//统计每个桶的数据个数
{
size_t index = (arr[i]/div) % 10;
beginIndex[index]++;
}
int index = 0;
for(int i = 0; i < 10; ++i)//计算每个桶的开始下标
{
int count = beginIndex[i];
beginIndex[i] = index;
index += count;
}
//将单次排序数据写入临时数组
for(int i = 0; i < size; ++i)
{
size_t index = (arr[i]/div) % 10;
tmp[beginIndex[index]++] = arr[i];
}
div *= 10;
//将tmp的数据拷贝到arr中
for(int i =0 ;i < size; ++i)
{
arr[i] = tmp[i];
}
}
delete[] beginIndex;
delete[] tmp;
}