时间复杂度分析
选择排序
引言
选择排序(Selection Sort)是一种简单直观的排序算法。他首先在未排序的序列中找到最小元素,存放到排序序列的起始位置,然后再找到剩下序列中最小的元素,放到排好序列的下一个位置。以此类推,直到所有元素排序完毕。
算法思想
第一步、在未排序的序列中找到最小(最大)的元素,存放到排序序列的起始位置。
第二步、再从剩余未排序元素中找到最小(最大)的元素,然后放到已排序的序列的末尾。
重复第二步,直到所有元素都排序完毕。
动图展示
代码实现 (升序)
-
void Select(int* a, int n)
-
{
-
for (int i = 0; i < n - 1; i++)//排前n-1的数
-
{
-
for (int j = i + 1; j < n; j++)
-
{
-
if (a[j] < a[i])
-
{
-
swap(a[i], a[j]);//未排序的序列中比目标小的就交换。
-
}
-
}
-
}
-
}
优化
上述代码在未排序的序列中找最小(最大)的元素,每一轮只能找到一个,还可以将其进行优化,一轮中可以同时找到最小和最大的元素,分别将其交换到未排序序列的头部和尾部.
代码实现
-
//选择排序
-
void chosesort(int* a, int n)
-
{
-
int begin = 0; int end = n - 1;
-
int mini = begin;
-
int maxi = begin;
-
while (begin < end)
-
{
-
maxi = begin; mini = begin;
-
for (int i = begin + 1; i <=end; i++)
-
{
-
if (a[i] > a[maxi])maxi = i;
-
if (a[i] < a[mini])mini = i;
-
}
-
if (begin ==maxi && end ==mini)swap(a[begin], a[end]);
-
else
-
{
-
swap(a[begin], a[mini]);
-
swap(a[end], a[maxi]);
-
}
-
begin++; end--;
-
}
-
}
分析
将未排序的序列的两端使用下标begin,end表示出来,注意我们找未排序序列中的最大值和最小值的时候找的是其下标并不是值,找到后将最大值(下标为maxi)和下标为end的值进行交换,将最小值(下标为mini)和下标为begin的值进行交换。
如果最大值恰好在begin位置,最小值恰好在end位置那么就只需要交换一次就可以了、
冒泡排序
引言
冒泡排序(Bubble Sort)是一种简单的排序算法,同过多次比较和交换相邻的元素,将数组中的元素按升序或降序排序。
算法思想
冒泡排序的基本思想是:每次遍历数组,比较相邻的两个元素,如果他们的顺序错误,就将他们交换,直到数组的所有元素都被遍历过。
动图展示
代码实现
-
//冒泡排序
-
void bubllesort(int* a, int n)
-
{
-
for (int i = 0; i < n - 1; i++)
-
{
-
for (int j = 0; j < n - i - 1; j++)
-
{
-
if (a[j] > a[j + 1])
-
swap(a[j], a[j + 1]);
-
}
-
}
-
}
插入排序
引言
插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作原理是通过构有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入,直到整个数组有序。
算法思想
第一步:从第一个元素开始,将其看做已排序部分.
第二步:取出下一个元素,与已排序的元素进行比较。
第三步:如果该元素小于已排序部分的最后一个元素,则将其插入到已排序的适当位置。
重复步骤二和三,知道整个数组都排好。
动图展示
代码实现
-
//插入排序
-
void Insertsort(int* a, int n)
-
{
-
for (int i = 1; i < n; i++)
-
{
-
int j;
-
int tmp = a[i];
-
for (j = i - 1; j >= 0; j--)
-
{
-
if (a[j] > tmp)
-
{
-
a[j + 1] = a[j];
-
}
-
else
-
break;
-
}
-
a[j + 1] = tmp;
-
}
-
}
计数排序
引言
计数排序(Counting Sort)是一种基于哈希的排序算法。它的基本思想是通过统计每个元素的出现次数,然后根据统计结果将元素一次放到排序后的序列中。这种算法使用与元素范围较小的情况,例如0到k之间。
算法思想
计数排序的核心是创建一个计数数组。用于记录没每个元素出现的次数、然后根据计数数组对原始数组进行排序。具体步骤如下:
第一步:初始化一个长度为最大元素加1的计数数组,所有元素初始化为0.
第二步:遍历数组,将每个元素的值作为索引,在计数师叔祖的对应位置加1。
第三步;将原数组清空。
第四步:遍历计数器数组,按照数组中元素的个数放回到原数组中。
动图展示
代码实现
-
int * countingSort1(int arr[],int count,int max) {
-
int index = 0;
-
int *tmpArr = (int *)malloc(max*sizeof(int));
-
int *result = (int *)malloc(max*sizeof(int));
-
for(int k = 0;k<max;k++) {
-
tmpArr[k] = 0;
-
}
-
for (int i = 0; i<count; i++) {
-
tmpArr[arr[i]]++;
-
}
-
for (int j = 0; j<max; j++) {
-
while (tmpArr[j]) {
-
result[index++] = j;
-
tmpArr[j]--;
-
}
-
}
-
free(tmpArr);
-
tmpArr = NULL;
-
return result;
-
}
桶排序
引言
桶排序(Bucket Sort)是一种基于计数的排序算法,工作原理是将数据分到有限数量的桶子里,然后将桶子在分别排序(有可能在使用别的排序算法或者是以递归方式继续使用桶排序进行排序)。
算法思想
第一步:设置固定的空桶。
第二步:把数据放到对应的桶中。
第三步:把每个不为空的桶中的数据进行排序。
第四步:拼接不为空的桶中的数据,得到结果。
动图展示
代码实现
-
void bucketsort(vector<int>& nums) {
-
int n = nums.size();
-
int mn = nums[0], mx = nums[0];
-
for (int i = 1; i < n; i++) {
-
mn = min(mn, nums[i]);
-
mx = max(mx, nums[i]);
-
}
-
int size = (mx - mn) / n + 1; // size 至少要为1
-
int cnt = (mx - mn) / size + 1; // 桶的个数至少要为1
-
vector<vector<int>> buckets(cnt);
-
for (int i = 0; i < n; i++) {
-
int idx = (nums[i] - mn) / size;
-
buckets[idx].push_back(nums[i]);
-
}
-
for (int i = 0; i < cnt; i++) {
-
sort(buckets[i].begin(), buckets[i].end());
-
}
-
int index = 0;
-
for (int i = 0; i < cnt; i++) {
-
for (int j = 0; j < buckets[i].size(); j++) {
-
nums[index++] = buckets[i][j];
-
}
-
}
-
}
基数排序
引言
基数排序(Radix Sort)是一种非比较型的排序算法,他根据数字的每一位来进行排序,通常用于整数排序,基数排序的基本思想就是通过对所有元素进行若干次“分配”和“收集”操作来实现排序。
算法思想
基数排序的算法思想可以概括为以下步骤:
第一步:获取待排序元素的最大值,并确定其位数。
第二步:从最低位开始,依次对所有的元素进行“分配”和“收集”操作。
第三步:在每一位上,根据该位置上的值将元素分配到相应的桶中。
第四步:在每个桶中的元素进行顺序收集,得到排序后的部分结果、
重复上述操作,直到所有所有位都排好。
动图展示
代码实现
-
int main()
-
{
-
int a[] = { 1,6,4,12,32,16,10,89,100,120 };
-
int b[10];
-
int sum[256] = { 0 }, sum1[256] = { 0 }, sum2[256] = { 0 }, sum3[256] = { 0 };
-
for (int i = 0; i <= 9; i++) {
-
++sum[a[i] & 255];
-
++sum1[(a[i] >> 8) & 255];
-
++sum2[(a[i] >> 16 )& 255];
-
++sum3[(a[i] >> 24) & 255];
-
}
-
for (int i = 1; i <= 255; i++)
-
{
-
sum[i] += sum[i - 1];
-
sum1[i] += sum[i - 1];
-
sum2[i] += sum[i - 1];
-
sum3[i] += sum[i - 1];
-
}
-
for (int i = 0; i <9; i++)
-
b[--sum[a[i] & 255]] = a[i];
-
for (int i = 9; i >= 0; i--)
-
a[--sum1[(a[i] >> 8) & 255]] = b[i];
-
for (int i = 9; i >= 0; i--)
-
b[--sum2[(a[i] >> 16) & 255]] = a[i];
-
for (int i = 9; i >= 0; i--)
-
a[--sum3[(a[i] >> 24) & 255]] = b[i];
-
for (int i = 0; i <= 9; i++)
-
printf("a[%d]=%d\n", i, a[i]);
-
getchar();
-
return 0;
-
}
希尔排序
引言
希尔排序(Shell Sort):是直接插入排序的一种优化,希尔排序通过基于直接插入排序间隔性的将非顺序序列的元素进行排序。需要进行多次排序,每次都更加的接近顺序,直到排好。
算法思想
首先创造变量gap作为将每隔gap个数分到一组中,将序列分为若干组,然后在每组中使用直接插入排序,同时每次使用完直接插入排序后都要减小gap的值(通常是gap/=2或者是gap=gap/3+1);直到最后gap为1,此时就是将一个十分接近顺序的序列进行插入排序,就排好啦。
动图展示
代码实现
-
void slove(int* a, int n)
-
{
-
int gap = n;//通常将gap初始化为数组的长度
-
while (gap > 1)//最后一次gap=1,就是直接插入排序
-
{
-
gap = gap / 3 + 1;//每次都要减少对应的间距
-
for (int i = 1; i < n; i++)//直接插入排序
-
{
-
int j;
-
int tmp = a[i];
-
for (j = i - gap; j >= 0; j -= gap)
-
{
-
if (a[j] > tmp)
-
{
-
a[j + gap] = a[j];
-
}
-
else
-
break;
-
}
-
a[j + gap] = tmp;
-
}
-
}
-
}
快速排序
引言
快速排序(Quick Sort)是一种分而治之的排序算法。它通过选择一个基准元素,将数组分为两部分,一部分比基准销小,另一部分比基准大,然后对这两部分进行快速排序,最终得到有序的数组。
算法思想
第一步、选择基准元素;从数组中选择一个元素作为基准。
第二步、分割数组:将比基准小的元素放在基准的左边,比基准大的元素放在基准的右边。
第三步、递归排序:对基准左边和右边的子数组分别进行快速排序。
重复步骤一、二、三、直到子数组的长度为1或0。
动图展示
动图分析
这里选择数组首元素6的位置作为基准元素的位置,接下来目的就是把数组分左边小于基准元素右边大于基准元素。
左边指针为L右边指针为R,这里注意远离基准元素位置的指针要先走,R先走,从右向左找比a[keyi]小的数然后停下,L走,L从左向右走,找到比a[keyi]大的数然后停下。将两者位置上的值进行交换。然后继续R走,L走,直到两个指针相遇。
相遇后将这个位置的数与基准位置上的数进行交换。
然后再递归左边和右边的序列即可。
代码实现
-
void Quicksort(int* a, int left, int right)
-
{
-
//改为左边先走即可(keyi右端点)
-
if (left >= right)return;
-
int keyi = left; //选取基准位置
-
int begin = left;
-
int end = right;
-
while (left < right)
-
{
-
while (left < right && a[right] > a[keyi] )
-
{
-
right--;
-
}
-
while (left < right && a[left] < a[keyi])
-
{
-
left++;
-
}
-
swap(a[left], a[right]);
-
}
-
swap(a[left], a[keyi]);
-
keyi = left;
-
Quicksort(a, begin, keyi - 1);
-
Quicksort(a, keyi + 1, end);
-
}
堆排序
引言
堆排序(Heap Sort)就是对直接选择排序的一种改进。此话怎讲呢?直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的,但是在选出最大或者最小的数后,并没有对原来的序列进行改变,这使得下一次选数时还需要对全部数据进行比较,效率大大降低。堆排序算法是Floyd和Williams在1964年共同发明的,同时他们发明了“堆”这种数据结构。
大堆和小堆
这里简单的说下什么是小堆什么是大堆。小堆就是父节点的值比两个孩子节点的值都要小,孩子节点之间无大小比较;大堆就是父节点比两个孩子的节点的值都要大,孩子节点之间无比较。
算法思想
堆排序使用到二叉树的调整算法,由于向下调整算法更优,这里只给出向下调整算法,向下调整算法的目的是为了建堆;建小堆排好是降序,建大堆排好是升序。
第一步、建好大堆:使用向下调整算法实现建堆。
第二步、交换头尾元素:每次建好堆之后我们可以确定的是堆顶一定是值最大的元素,将他交换到数列的末尾。
第三步、再次建堆:交换首尾之后,原来的堆就遭到了破坏,因此我们需要再次重新建堆,需要注意的是这一次建堆元素的个数就减一,因为刚才交换到最后的最大值已经算是排好的了,因此我们需要将剩下的元素建堆。
重复上述操作n-1次就排好了。
动图展示
代码实现
-
//堆排序(升序)
-
void adjustdown(int* a, int parent,int n)
-
{
-
int child = 2 * parent + 1;
-
while (child < n)
-
{
-
if (child + 1 < n && a[child] < a[child + 1])
-
child++;
-
if (a[child] > a[parent])
-
{
-
swap(a[child], a[parent]);
-
parent = child;
-
child = child * 2 + 1;
-
}
-
else
-
break;
-
}
-
}
-
void heapsort(int* a, int n)
-
{
-
int end = n;
-
while (end > 0)//堆不为空时
-
{
-
for (int i = (end - 2) / 2; i >= 0; i--)
-
adjustdown(a, i, end);//先向下调整
-
end--;//调整完之后
-
swap(a[0], a[end]);//下一次要调整的堆的个数就是这一次要交换的目标值
-
}
-
}
归并排序
引言
归并是一种常见的算法思想,在许多领域都有广泛的应用。归并排序的主要目的是将两个已排序的序列合并成有序的序列。
算法思想
当然,对于一个非有序的序列,可以拆成两个非有序的序列。然后分别调用归并排序,然后对两个有序的序列在执行合并的过程。所以这里的“归”其实是递归,“并”就是合并。
整个算法的执行过程用mergeSort(a[],l,r)描述,代表当前代排序的数组a,左下标i,右区间下标r,分为以下几步。
第一步:计算中点mid=(l+r)/2;
第二步:递归调用mergeSort(a[],l,mid)和mergeSort(a[],mid+1,r).
第三步:将第二步中的两个数组进行合并,在储存在a[l:r]。
调用时,调用mergeSort(a[],0,n-1)就能得到整个数组的排序结果了。
动图展示
代码展示
-
void merge(int*a,int begin,int mid,int end)
-
{
-
int* b=new int[end-begin+1];//开辟区间所占整形大小的空间
-
int i=begin,j=mid+1;int k=0;//设置遍历下标初始值
-
while(i<=mid&&j<=end)//对辅助数组处理
-
{
-
if(a[i]<a[j])
-
b[k++]=a[i++];
-
else
-
b[k++]=a[j++];
-
}
-
while(i<=mid)//把多余的一半放到辅助数组中
-
b[k++]=a[i++];
-
while(j<=end)
-
b[k++]=a[j++];
-
k=0;
-
for(int i=begin;i<=end;i++)
-
a[i]=b[k++];
-
}
-
void mergesort(int*a,int begin,int end)
-
{
-
if(begin<end)
-
{
-
int mid=(begin+end)/2;
-
mergesort(a,begin,mid);//裂开!
-
mergesort(a,mid+1,end);
-
merge(a,begin,mid,end);//合体!
-
}
-
else
-
return;
-
}