一、快速排序(分治思想)
1.1 基于挖坑法的单趟排序思想:形成以key为中心,左边序列都比key小,右边序列都比key大
1.1.1 设置最左边为坑位,并保存该位置的值到key中
1.1.2 右指针走(每次都应要求L<R),找比key小的位置
1.1.3 找到后,将该值放入坑位中,该位置形成新的坑位
1.1.4 L向后移动(每次都应要求L<R),找比key大的位置
1.1.5 找到后,将该值放入坑位中,该位置形成新的坑位
1.1.6 循环第2~5条
1.1.7 L与R相遇,将key值放入坑位
挖坑法动图
int partsort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
swap(&a[left], &a[mid]);
int key = a[left];
int hole = left;
while (left < right)
{
// 右指针走,找比key小的位置
while (left < right && key <= a[right])
{
right--;
}
// 找到后,将该值放入坑位中,该位置形成新的坑位
a[hole] = a[right];
hole = right;
// L向后移动,找比key大的位置
while (left < right && key >= a[left])
{
left++;
}
// 找到后,将该值放入坑位中,该位置形成新的坑位
a[hole] = a[left];
hole = left;
}
// L与R相遇,将key值放入坑位
a[hole] = key;
return hole;
}
挖坑单趟排序代码
1.2基于前后指针法的单趟排序思想:形成以key为中心,左边序列都比key小,右边序列都比key大
1.2.1 设置前(prev)后(cur)指针与key变量,
1.2.2 后指针cur一直走,cur停下来条件:a[cur]小于key并且cur与prev不紧跟,至少相差1个位置以上(因为从开始cur==prev+1)
1.2.3 prev指针自增,交换a[prev]与a[cur]
1.2.4 cur越界后,交换a[prev]与key的值
前后指针法动图
int partsort3(int* a, int left, int right)
{
// 1.初始,prev指向序列开头,cur指向prev的后一个位置
// 2.cur一直找小,prev紧跟cur;cur若找到大的,prev不动
// 3.cur继续往后走,遇到比key小的,交换cur与prev的值
// 4.cur越界,交换prev与key的值
int mid = GetMidIndex(a, left, right);
swap(&a[left], &a[mid]);
int key = a[left];
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < key && ++prev!=cur) // ++prev!=cur意为把prev与cur中间没有隔着比key大的数据的情况排除
{
prev++;
swap(&a[prev], &a[cur]);
}
cur++;
}
swap(&a[prev], &a[left]);
return prev;
}
前后指针法代码
1.3.基于hoare法的单趟排序思想:形成以key为中心,左边序列都比key小,右边序列都比key大
1.3.1 设置左(left)右(right)变量,left指向最左边,right指向最右边;设置key指向左边第一个
1.3.2 right先走找比key小的数,left再走找比key大的树
1.3.3 两边都找到后,交换left与right的值
1.3.4 重复3.2与3.4,直到left与right相遇
1.3.5 相遇,交换left与key的值
hoare法动图
int partsort1(int* a,int left,int right)
{
int mid = GetMidIndex(a,left,right);
swap(&a[left],&a[mid]);
int key = a[left];
while(left < right)
{
while(left < right && a[right] >= key)
{
right--;
}
while(left < right && a[left] <= key)
{
left++;
}
if(left < right)
{
swap(&a[left],&a[right]);
}
}
int meet = left;
swap(&key,&a[left]);
return meet;
}
hoare法代码
1.4. 基于递归实现快速排序:递归结束条件为left>=right,递归实现很像二叉树的前序遍历,每一趟都会确定一个key,通过key确定左右子树。
void quicksort(int* a,int begin,int end)
{
if(begin >= end)
return;
int key = partsort(a,begin,end);
quicksort(a,beign,key - 1);
quicksort(a,key + 1,end);
}
1.5. 基于非递归实现快速排序:用栈来实现非递归版:核心在于控制key、begin、end三个变量的变化,根据递归思想,这三个值变化的规律是能分割出数组各自的段[begin,key-1]、[key]、[key+1,end]。初始begin与end先后入栈,然后获取栈顶元素放入right、随后出栈,再获取栈顶元素left、随后出栈,根据这两个数组下标调用partsort函数,确定新的key以及新的数组[begin,key-1]、[key]、[key+1,end]段,以上步骤进入循环(结束条件:栈不为空) 代码如下:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef struct Stcak
{
int* a;
int top;
int capacity;
}Stcak;
void Stackpush(Stack* ps,int x);
void Stackpop(Stack* ps);
void StackInit(Stack* ps);
void Stackdestory(Stack* ps);
int StackTop(Stack* ps);
int StackEmpty(Stack* ps);
int partsort(int* a,int left,int right);// 代码实现见上文
void quicksort(int* a,int begin,int end);
void test1()
{
Stack ps;
Stackpush(&ps,x);
Stackpop(&ps);
StackInit(&ps);
Stackdestory(&ps);
int tmp = StackTop(&ps);
}
void Stackpush(Stack* ps,int x)
{
assert(ps);
if(ps->top > ps->capacity)
{
int* tmp = cheackcapacity(a);
}
else
ps->a[ps->top] = x;
}
void Stackpop(Stack* ps)
{
assert(ps);
if(ps->top > 0)
{
ps->top--;
}
}
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
void Stackdestory(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
int StackTop(Stack* ps)
{
assert(ps);
if(ps->top > 0)
{
return ps->a[a->top];
}
}
int StackEmpty(Stack* ps)
{
assert(ps);
if (ps->top == 0)
{
return 1;
}
else
return 0;
}
void quicksort(int* a,int begin,int end)
{
Stack ps;
StackInit(&ps);
Stackpush(&ps,end);
Stackpush(&ps,begin);
while(!StackEmpty(&st))
{
int left = Stacktop(&ps);
Stackpop(&ps);
int right = stacktop(&ps);
Stackpop(&ps);
int key = partsort(a,left,right);
if (key + 1 < right)
{
StackPush(&st, key + 1);
StackPush(&st, right);
}
if (key - 1 > left)
{
StackPush(&st, left);
StackPush(&st, key - 1);
}
}
}
二、希尔排序(插入排序)
希尔排序是直接插入排序的改进版,核心在引入gap变量,gap变量将数组数据分为gap组;
单趟排序是对这gap组的数据按照直接插入进行排序,多趟排序在于减小gap变量值进行排序,当gap==1时,此时就是直接插入排序。
希尔排序逻辑图
void shellsort(int* a,int x)
{
int gap = 3;
for(int i = 0;i < gap;i++)
{
// 单组
for(int j = i;j < x - gap;j += gap)
{
int end = j;
int tmp = a[end+gap];
// 单趟1个数字的插入-要插入的数小于end
while(end >= 0)
{
if(a[end+1] < tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
// 要插入的数大于end
a[end+1] = tmp;
}
}
}
希尔排序代码
三、堆排序(选择排序)
堆首先是完全二叉树,结构上采用动态数组的方式,所以访问堆可采用访问数组下标的方式,因此父节点与孩子结点的数组下标遵从:child = parent * 2 + 1 or parent * 2 + 2,同理:parent = (child - 1) / 2,给访问带来方便。建堆方式选择向下调整,主要原因还是时间复杂度小。另外升序建大堆、降序建小堆。思考时应该始终考虑物理结构为数组,所有操作基于数组实现,堆这种数据结构帮助我们实现操作中的某种特点操作。
3.1 总体采用向下调整建大堆以实现数组数据的升序操作
3.2 大堆第一个数最大的性质,交换数组第一个数与最后一个数,数组元素减减
3.3 使用向下调整保持大堆性质
3.4 找出次大的数与最后一个数交换,数组元素减减
3.5 直到,数组元素为0停止
// 向下调整
void Ajustdown(int* a,int n,int parent)// 这里的parent是数组下标0
{
int maxchild = parent * 2 + 1;
while(maxchild > 0)
{
// 找当前parent下面两个孩子的最大的孩子
if(a[maxchild] < a[maxchild + 1] && maxchild + 1 < n)
{
maxchild++;
}
if(a[maxchild] > a[parent])
{
swap(&a[maxchild],&a[parent]);
parent = minchild;
minchild = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int* a,int n)
{
assert(a);
// 从最后一个结点的父节点向下调整升序建大堆
for(int i = (n - 1 - 1) / 2;i > 0;i--)
{
Ajustdown(a,n,i);
}
// 排序
int i = 1;
while(i < n)
{
swap(&a[0],&a[n - i]);
Ajustdown(a,n - i,0);
i++;
}
}
堆排序代码
四、直接插入排序
假设数组从[0,end]有序(注意:end不是一个固定的值,通过下图可以发现end为黄色的长度),我们需要在end+1位置处进行插入,则有如下操作:
4.1 先将end+1位置处数据保存到key,判断end+1位置处数据是否比end位置处数据大还是小
4.2 如果小:end位置处数据依次后移,end减减
4.3 如果大:将key插入到end+1位置处
void InsertSort(int* a,int n)
{
for(int i = 0;i < n - 1;i++)
{
// 一个数据的插入排序
int end = i;
int key = a[end + 1];
while(end >= 0)
{
// 判断end+1位置与end位置的数据大小
// 小于情况,实际不进行插入操作,只是寻找大于的end位置
if(key < a[end])
{
a[end + 1] = a[end];
end--;
}
else
break;
}
// 实际进行插入,key大于end位置的值情况
a[end + 1] = key;
}
}
直接插入排序代码
五、直接选择排序
直接选择排序优化的思想:依次遍历可以选出最大与最小的数据,最大数放最后,最小数放最前,交换之前应保存要交换的下标
void selection(int* a,int n)
{
int begin = 0,end = n - 1;
while(begin < end)
{
// 设置两个中间变量mini与maxi
int mini = begin,maxi = end;
for(int i = begin + 1;i <= end;i++)
{
// 比较
if(a[i] < a[mini])
{
mini = i;
}
if(a[i] > a[maxi])
{
maxi = i;
}
// 交换
swap(&a[begin],&a[mini]);
if(mini == begin)
{
maxi = mini;
}
swap(&a[end],&a[maxi]);
// 继续走
begin++;
end--;
}
}
}
六、冒泡排序
相邻两两进行比较,一趟排一个数,下一趟去除掉排好了的数。优化:若数据整体有序,一趟下来没有进行交换,则认为有序
void Bubblesort(int* a,int n)
{
for(int i = 0;i < n-1;i++)
{
int flag = 0;
for(int j = 0;j < n - i - 1;j++)
{
if(a[j + 1] > a[j])
{
swap(&a[j + 1],&a[j]);
flag++;
}
}
if(flag == 0)
break;
}
}
七、归并排序
7.1 递归版--类似与快速排序,若左区间与右区间有序,取两个区间中较小的数据,尾插到新建的数组中。若左右区间没有序,可以采用递归进行有序操作。递归操作最终将会把数组中的数据分成一个一个的数据,这样一个数据则认为有序。归并操作:每段区间归并时,相互比较,取小(大)的进行插入临时数组中,后再拷贝到原数组中,由于两段区间长度可能不同,应考虑长区间没有走完的情况
7.2 非递归版--采用直接改循环的方式,模拟递归的分治思想,引入变量gap来控制递归思想中的区间边界(gap=1意味着组内1个元素、gap=2组内2个元素、gap=4组内4个元素...),做到从一个一个数据归并、再到两个两个数据归并、再到四个四个数据归并....
,
void _Mergesort(int* a,int begin,int end,int* tmp)
{
if(begin == end)// 递归下去发现每个区间里面只有一个数据
{
return;
}
// 先分区间
int mid = (begin + end) / 2;
// 递归中的递
_Mergesort(a,begin,mid,tmp);
_Mergesort(a,mid + 1,end,tmp);
// 归并
int begin1 = begin,end1 = mid;
int begin2 = mid + 1,end2 = end;
int i = begin;
while(begin1 <= end1 && begin2 <= end2)
{
// 两个区间比
if(a[begin1] <= a[begin2])
{
tmp[i] = a[begin1];
i++;
begin1++;
}
else
{
tmp[i] = a[begin2];
i++;
begin2++;
}
}
// 长区间插入
while(begin1 <= end1)
{
tmp[i] = a[begin1];
i++;
begin1++;
}
// 长区间插入
while(begin2 <= end2)
{
tmp[i] = a[begin2];
i++;
begin2++;
}
// 拷贝回原数组--归并哪部分就拷贝哪部分回去
memcpy(a + begin,tmp + begin,(end - begin + 1)*sizeof(int));
}
void Mergesort(int* a,int begin,int end,int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if(tmp == NULL)
{
exit(-1);
}
int begin = 0;
int end = n - 1;
_Mergesort(a,0,n-1,tmp);
free(tmp);
tmp = NULL;
}
递归版归并排序代码
归并排序-非递归版走读代码图
void Mergesort(int* a,int begin,int end,int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if(tmp == NULL)
{
exit(-1);
}
int gap = 1;
while(gap < n)
{
for(int j = 0;j < n - 1;j += 2 * gap)
{
int begin1 = j,end1 = j + gap - 1;
int begin2 = j + gap,end2 = j + 2 * gap - 1;
int i = j;
// 第一组越界
if (end1 >= n)
{
printf("[%d,%d]", begin1, n - 1);
break;
}
// 第二组全部越界
if (begin2 >= n)
{
printf("[%d,%d]", begin1, end1);
break;
}
// 第二组部分越界
if (end2 >= n)
{
// 修正一下end2,继续归并
end2 = n - 1;
}
printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
while(begin1 <= end1 && begin2 <= end2)
{
if(a[begin1] >= a[begin2])
{
tmp[i] = a[begin2];
i++;
begin2++;
}
else
{
tmp[i] = a[begin1];
i++;
begin1++;
}
}
while(begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while(begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 拷贝回原数组--归并哪部分就拷贝哪部分
memcpy(a + j, tmp + j, (end2 - j + 1) * sizeof(int));
}
gap = gap * 2;
}
free(tmp);
tmp = NULL;
}
非递归版归并排序代码
八、计数排序
假如有这样的一组数据:[21,20,22,24,26,23,21,20,29] or like this [100,110,115,110,120,125,120] or like this [150,155,155,157,156,140,144,140].....他们都有这样的共同点那就是数据范围相对集中,这样的数据适用于计数排序。
计数排序的思想:新建数组用于存放原数组中各元素出现的次数,新建数组的下标从0到原数组中最大的数据值。for example:原数组[21,20,22,24,26,23,21,20,29],则新建数组创建从0到29的数组空间,为避免空间浪费,引入相对映射,则开辟max-min+1(29-20+1==10)个空间,映射相对位置a[i]-min.
void countsort(int* a,int n)
{
int maxi = a[0],mini = a[0];
for(int i = 1;i < n;i++)
{
if(mini > a[i])
{
mini = a[i];
}
if(maxi < a[i])
{
maxi = a[i];
}
}
int range = maxi - mini + 1;
int* countA = (int*)malloc(sizeof(int)*range);
for(int i = 0;i < n;i++)
{
// 统计次数
countA[a[i] - mini]++;
}
int j = 0;
for(int i = 0;i < range;i++)
{
a[j] = i + mini;//新数组中的下标+mini等于原数组中的数据
j++;
}
}