目录
一、冒泡排序
基本思想:
主要思路:
二、选择排序
基本思想:
主要思路:
三、插入排序
基本思想:
主要思路:
插入排序优化:
四、希尔排序
基本思想:
思路步骤:
代码示例:
对于希尔排序稳定性的解释:
五、归并排序
基本思想:
主要思路:
六、快速排序
基本思想:
1.指针交换法
主要思路:
步骤图解:
2.挖坑法
二分查找(折半)
堆排序详细图解(通俗易懂)_堆排序过程图解
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。每一趟排序后的效果都是讲没有沉下去的元素给沉下去。
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。
2.对每一个相邻元素做同样的工作,从开始第一对到结尾的每一对。在这一 点,最后的元素应该会是最大的数。
3.针对多有的元素重复以上的步骤,除了最后一个。
//冒泡排序***********************************
void bubble_sort(int arr[], int n)
{
int temp = 0;//保存中间值
for (int i = 0; i arr[j+1])
{
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
//另一种写法*************
void bubble_sort1(int arr[], int n)
{
int temp = 0;//保存中间值
for (int i = 0; i < n - 1; i++) //从第一个开始往后依次比较 1和2 1和3... 2和3 ...
{
for (int j = i + 1; j < n; j++)
{
if (arr[i] > arr[j])
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
int main()
{
int n;
printf("请输入数组长度:");
scanf("%d", &n);
int* p=(int*)malloc(n * sizeof(int));
printf("请输入待排数据:");
for (int i = 0; i < n; i++)
{
scanf("%d", &p[i]);
}
bubble_sort(p, n);
//bubble_sort1(p, n);
printf("排序后为:");
for (int i = 0; i < n; i++)
{
printf("%d ", p[i]);
}
free(p);
return 0;
}
程序实现:
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始位置。
3.重复第二步,直到所有元素均排序完毕。
时间复杂度O(n^2), 不稳定
代码示例:
void selection_sort1(int arr[], int n)
{
int temp = 0;//保存中间值
for (int i = 0; i < n - 1; i++)
{
int mindex = i;//假设第一个元素是最小的
for (int j = i + 1; j < n; j++)//第i个数要和后面的每个值比较,与i比较的值记作j
{
if (arr[mindex] > arr[j])//若后面的数j更小,则
{
mindex = j;//记住此时j的下标
}
}
temp = arr[mindex];
arr[mindex] = arr[i];//将a[i]和a[j]交换位置
arr[i] = temp;
}
}
int main()
{
int n;
printf("请输入数组长度:");
scanf("%d", &n);
int* p = (int*)malloc(n * sizeof(int));
printf("请输入待排数据:");
for (int i = 0; i < n; i++)
{
scanf("%d", &p[i]);
}
selection_sort1(p, n);
printf("排序后为:");
for (int i = 0; i < n; i++)
{
printf("%d ", p[i]);
}
free(p);
return 0;
}
程序实现:
将待排序的无序数列看成是一个仅含有一个元素的有序数列和一个无序数列,将无序数列中的元素逐次插入到有序数列中,从而获得最终的有序数列。
插入排序是最简单常用的方法,将数组分为两部分,排好序的数列,以及未排序的数列,将未排序的数列中的元素 与排好序的数列进行比较,然后将该元素插入到已排序列的合适位置中。
1.从第一个元素开始,该元素可以认为已经被排序
2.取下一个元素temp,从已排序的元素序列从后往前扫描
3.如果该元素大于temp,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于temp的元素
5.temp插入到该元素的后面,如果已排序所有元素都大于temp,则将temp插入到下标为0的位置
6.重复步骤2~5
时间复杂度O(n^2), 稳定
图示:
浅绿色代表有序部分,黄色代表无序部分。
在无序部分中挑出要插入到有序部分中的元素
将要插入的元素与左边最近的有序部分的元素进行比较。由于4 < 9,所以9向后移,4向前移
继续将要插入的元素与左边最近的有序部分的元素进行比较。由于4 < 5,所以5向后移,4继续向前移
继续将4与3比较。由于4 > 3,所以不再向前比较,插入到当前位置
此时有序部分,由[2,3,5,9]变成[2,3,4,5,9]
代码示例:
void Input(int arr[], int n) //输入函数
{
printf("请输入待排数据:");
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
}
void Show(int arr[], int n) //打印函数
{
printf("排序后为:");
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
//插入排序**********************
void Insert(int arr[], int n)
{
int j;
int temp;//保存要插入的元素
for (int i = 1; i < n; i++)//第一个元素视为有序,把后面的元素一个一个的插入到前面
{
temp = arr[i];
for(j=i-1;j>=0;j--)
{
if (arr[j] > temp) //比要插入的元素大 后移
{
arr[j + 1] = arr[j];//前面的元素往后面移动
}
else //比插入的元素小 退出while
break;
}
arr[j + 1] = temp; // 把要插入的元素, 插入进对应的位置
}
}
int main()
{
int n;
printf("请输入数组长度:");
scanf("%d", &n);
int* p = (int*)malloc(n * sizeof(int));
Input(p, n);
Insert(p, n);
Show(p, n);
free(p);
return 0;
}
程序演示:
折半插入排序:该类优化有二分的思想,是在将待排序的元素与有序部分的元素比较时,不再挨个比较,而是用二分折中的方式进行比较,加快比较效率。
代码示例:
//使用二分法插入排序
void BinaryInsert(int arr[],int n)
{
int j;
int low, mid, high;
int temp; //保存每次要插入的数
for (int i = 1; i < n; i++)
{
low = 0;
high = i - 1;
temp = arr[i];
/*找到合适的插入位置high+1,如果中间位置元素
*比要插入元素大,则查找区域向低半区移动,否
*则向高半区移动
*/
while (low <= high)
{
mid = (low + high) / 2;
if (arr[mid] > temp)
{
high = mid - 1;
}
else
low = mid + 1;
}
//high+1后的元素后移
for (j = i - 1; j >= high + 1; j--)
{
arr[j + 1] = arr[j];
}
//将元素插入到指定位置
arr[j + 1] = temp;
}
}
希尔排序,是插入排序的一种进阶排序算法,通过一个不断缩小的增量序列,对无序序列反复的进行拆分并且对拆分后的序列使用插入排序的一种算法,所以也叫作“缩小增量排序”或者“递减增量排序”。时间复杂度为O(n^1.3~n^1.5),要好于直接排序的O(n ^ 2),需要注意的是增量序列的最后一个增量值必须是1.另外由于记录跳跃式的移动,希尔排序并不是一种稳定的排序方法。
所谓分组,就是让元素两两一组,同组两个元素之间的跨度,都是数组总长度的一半,也就是跨度为4。
如图所示,元素5和元素9一组,元素8和元素2一组,元素6和元素1一组,元素3和元素7一组,一共4组。
接下来,我们让每组元素进行独立排序,排序方式用直接插入排序即可。由于每一组的元素数量很少,只有两个,所以插入排序的工作量很少。每组排序完成后的数组如下:
这样一来,仅仅经过几次简单的交换,数组整体的有序程度得到了显著提高,使得后续再进行直接插入排序的工作量大大减少。这种做法,可以理解为对原始数组的“粗略调整”。
但是这样还不算完,我们可以进一步缩小分组跨度,重复上述工作。把跨度缩小为原先的一半,也就是跨度为2,重新对元素进行分组:
如图所示,元素5,1,9,6一组,元素2,3,8,7一组,一共两组。
接下来,我们继续让每组元素进行独立排序,排序方式用直接插入排序即可。每组排序完成后的数组如下:
此时,数组的有序程度进一步提高,为后续将要进行的排序铺平了道路。
最后,我们把分组跨度进一步减小,让跨度为1,也就等同于做直接插入排序。经过之前的一系列粗略调整,直接插入排序的工作量减少了很多,排序结果如下:
让我们重新梳理一下分组排序的整个过程:
像这样逐步分组进行粗调,再进行直接插入排序的思想,就是希尔排序,根据该算法的发明者,计算机科学家Donald Shell的名字所命名。
上面示例中所使用的分组跨度(4,2,1),被称为希尔排序的增量,增量的选择可以有很多种,我们在示例中所用的逐步折半的增量方法,是Donald Shell在发明希尔排序时提出的一种朴素方法,被称为希尔增量。
//直接(简单)插入排序
void InsertSort(int* arr, int len)//O(n^2),最好的情况,O(1),
{
int temp;//存放当前处理的数字
int j;
for (int i = 1; i < len; i++)//从第二个数字开始
{//1 2 3 4 5
temp = arr[i];
for (j = i - 1; j >= 0; j--)//从后往前找第一个比temp小的数字
{
if (arr[j] > temp)//arr[j]需要后移
arr[j + 1] = arr[j];
else
break;
}
arr[j + 1] = temp;
}
}
//gap:分组数
void Shell(int* arr, int len, int gap)
{
int temp;//保存当前需要处理的值
int j;
for (int i = gap; i < len; i++)//从"第二个"元素开始
{
temp = arr[i];
for (j = i - gap; j >= 0; j -= gap)
{
if (arr[j] > temp)
arr[j + gap] = arr[j];
else
break;
}
arr[j + gap] = temp;
}
}
//希尔排序
void ShellSort(int* arr, int len)//时间复杂度O(n^1.3~n^1.5),空间复杂度O(1),不稳定
{
//根据情况分组
int d[] = { 4,2,1 };//分组组数,注意最后一定是1.缩小增量
for (int i = 0; i < sizeof(d) / sizeof(d[0]); i++)
{
Shell(arr, len, d[i]);
}
}
在某些条件下,例如如下数组
如果我们照搬之前的分组思路,无论是以4为增量,还是以2为增量,每组内部的元素都没有任何交换。一直到我们把增量缩减为1,数组才会按照直接插入排序的方式进行调整。
对于这样的数组,希尔排序不但没有减少直接插入排序的工作量,反而增加了分组操作的成本。
因为希尔增量之间是等比的,这就导致了希尔增量存在盲区。为了避免这种情况,在每一轮的增量需要彼此“互质”,也就是没有除1之外的公约数。于是,人们相继提出了很多种增量方式,其中最具代表性的是Hibbard增量和Sedgewick增量。
Hibbard的增量序列如下:
1,3,7,15...... 通项公式 2^k-1
利用此种增量方式的希尔排序,最坏时间复杂度是O(n^(3/2))
Sedgewick的增量序列如下:
1, 5, 19, 41, 109......通项公式 9*4^k - 9*2^k + 1 或者 4^k - 3*2^k + 1
利用此种增量方式的希尔排序,最坏时间复杂度是O(n^(4/3))
上面数组当中,有两个元素5,绿色的5在前,橙色的5在后。
假如我们按照希尔增量分组,第一轮粗调(增量为4)之后,绿色的元素5会跟元素4交换,换到橙色的5后面:
第二轮粗调(增量为2)之后:
最终排序结果:
相同的元素5,在排序之后改变了次序,由此可见希尔排序是一个不稳定排序。
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用
分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到
完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成
一个有序表,称为二路归并。
时间复杂度O(nlogn),稳定
拆分:
合并:
如果有两个已经排序好的数组{1,3,4,8},{2,6,9,12};
我们要把这两个数组合并再排序;
目标数组应该是{1,2,3,4,6,8,9,12};
那是不是说,我们要把{1,4,6,8,2,7,9,12}这个数组,给按从小到大排序成为这个目标数组;
现在我们来实现一下!
我们用 i 来表示{1,3,4,8}中的第一个元素1;用 j 来表示{2,6,9,12}中的第一个元素2;
用 k 来表示新数组也就是待排序的数组的第一个元素;然后,重要的来了!
判断 i 和 j的大小;
如果i
最后,一定会有一个数组里面留下元素,例如上面这个{2,6,9,12}中会有9和12留下,最后再把9和12放在待排序的数组里面就好了!
因此代码实现我们需要变量left代表1,变量mid代表8,变量right代表12
left 到 mid 就是{1,3,4,8},mid+1到 right 就是{2,6,9,12}
上面是对于两个有序的数组进行归并操作,那么对于无序的数组我们应该怎么做呢?这就引入了我们的分治思想
假如我们有一个数组{5,2,6,1,4,6,3,7},将它分成两半去执行上面的排序功能,但是分一次我们会发现{5,2,6,1}和{4,6,3,7}依旧不是两个有序的数组,那么我们只需要再分再分!!
之后我们就需要递归不断调用分合分合的函数就可以啦
奉上代码示例:
void merge(int arr[],int brr[],int left, int mid, int right) //合并
{
int i, j, k;
i = left; //标记左半区第一个未排序元素
j = mid + 1; //标记右半区第一个未排序元素
k = left; //临时数组元素下标
//合并
while (i <= mid && j <= right)
{
if (arr[i] <= arr[j])
{
brr[k++] = arr[i++];
}
else
{
brr[k++] = arr[j++];
}
}
//合并左半区剩余元素
while (i <= mid)
{
brr[k++] = arr[i++];
}
//合并右半区剩余元素
while (j<=right)
{
brr[k++] = arr[j++];
}
//临时数组中元素赋值到原数组
for (int l = 0; l < right+1; l++)
{
arr[l] = brr[l];
}
}
//归并排序
void merge_sort(int arr[],int brr[], int left, int right)
{
//如果只有一个元素就不需要划分
if (left
程序测试:
快速排序算法的基本思想为分治思想。先从数列中取出一个数作轴值(基准数)key;
根据基准数将数列进行分区,小于基准数的放左边,大于基准数的放右边;
重复分区操作,知道各区间只有一个数为止。
平均时间复杂度O(nlogn),最差为O(n^2),不稳定
1、选出一个key,一般是最左边或是最右边的。
2、定义一个left和一个right,left从左向右走,right从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要right先走;若选择最右边的数据作为key,则需要left先走)。
3、在走的过程中,若right遇到小于key的数,则停下;left开始走,直到left遇到一个大于key的数时,交换left和right所指向的元素;right再次开始走,如此进行下去,直到left和right最终相遇,此时将key的内容与相遇点内容交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序。
我们首先选定基准元素Pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素:
接下来是第一次循环,从right指针开始,把指针所指向的元素和基准元素做比较。如果大于等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。
在当前数列中,1<4,所以right直接停止移动,换到left指针,进行下一步行动。
轮到left指针行动,把指针所指向的元素和基准元素做比较。如果小于等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。
由于left一开始指向的是基准元素,判断肯定相等,所以left右移一位。
由于7 > 4,left指针在元素7的位置停下。这时候,我们让left和right指向的元素进行交换。
接下来,我们进入第二次循环,重新切换到right向左移动。right先移动到8,8>4,继续左移。由于2<4,right停止在2的位置。
切换到left,6>4,left停止在6的位置。
元素6和2交换。
进入第三次循环,right移动到元素3停止,left移动到元素5停止。
元素5和3交换。
进入第四次循环,right移动到元素3停止,这时候请注意,left和right指针已经重合在了一起。
当left和right指针重合之时,我们让pivot元素和left与right重合点的元素进行交换。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。
代码示例:
void Input(int arr[], int n) //输入函数
{
printf("请输入待排数据:");
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
}
void Show(int arr[], int n) //打印函数
{
printf("排序后为:");
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
//快速排序 指针交换
void QuickSort(int* arr, int left, int right)
{
if (left > right)
{
return;
}
int i = left; //左指针
int j = right; //右指针
int key = arr[left]; //选取基准数 通常是数组第一个
while (i != j)
{
//控制右指针比较并左移
while (arr[j]>=key&&i
主要思路:
1.首先,我们选定基准元素key,这个位置相当于一个“坑”。并且设置两个指针left和right,指向数列的最左和最右两个元素。
2.接下来,从right指针开始,把指针所指向的元素和基准元素做比较。如果比key大,则right指针向左移动;如果比key小,则把right所指向的元素填入坑中。此时right的位置成为了新的坑位,同时,left向右移动。
3.接下来,我们切换到left指针进行比较。如果left指向的元素小于key,则left指针向右移动;如果元素大于key,则把left指向的元素填入坑中。这时候left的位置变成了坑位。同时,right向左移动。
4.按照这种思路一直进行下去,直到left和right重合在了同一位置。这时候,把之前的key元素,放进此时left和right重合重合的位置(即新坑位),这样一轮排序结束。
5.此时key的左边都是小于key的数,key的右边都是大于key的数。
6.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序。
将数组第一个数23赋给temp变量,指针 i 指向数组第一个元素,指针 j 指向数组最后一个元素
从 j 开始遍历(从右往左),遇到13时,因为13<=temp,因此将arr[j]填入arr[i]中,即此时指针 i 指向的数为13
再从 i 遍历(从左往右),遇到45时,因为45>temp,因此将arr[i]填入arr[j]中,此时指针 j 指向的数为45
继续从 j 遍历,遇到11时,因为11<=temp,因此将arr[j]填入arr[i]中,即此时指针 i 指向的数为11
从 i 遍历,遇到89时,因为89>temp,因此将arr[i]填入arr[j]中,此时指针 j 指向的数为89
从 j 遍历,遇到17时,因为17<=temp,因此将arr[j]填入arr[i]中,即此时指针 i 指向的数为17
从 j 遍历,遇到3时,因为3<=temp,因此将arr[j]填入arr[i]中,即此时指针 i 指向的数为3
从 i 遍历,遇到26时,因为26>temp,因此将arr[i]填入arr[j]中,此时指针 j 指向的数为26
从 j 遍历,和 i 重合
将 temp(基准数23)填入arr[i]中
此时完成算法的第2个步骤,接下来将23左边和右边的子区间分别用以上方法进行排序,直到区间只有一个元素即排序完成。
代码示例:
void Input(int arr[], int n) //输入函数
{
printf("请输入待排数据:");
for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
}
void Show(int arr[], int n) //打印函数
{
printf("排序后为:");
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
//快速排序 挖坑法
void QuickSort(int* arr, int left, int right)
{
if (left > right)
{
return;
}
int i = left; //左指针
int j = right; //右指针
int key = arr[i]; //选取基准数 通常是数组第一个
while (i < j)
{
while (arr[j]>=key&&i
相信看到这里应该已经明白了快排的算法