快速排序
快速排序的思想:
- 快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot)
- 然后对数组进行分区操作,使 基准左边元素的值都小于基准值, 基准
右边的元素值都大于基准值 ,如此作为基准的元素调整到排序后
的正确位置。
固定位置选取基准法
int Partion(int *arr, int low, int high)//找基准
{
int tmp = arr[low];//tmp用来保存第一个元素的值
while (low < high)
{
while (arr[high] > tmp&&low < high)
{
high--;
}
if (arr[high] < tmp)
{
arr[low] = arr[high];
}
else
{
break;
}
while (arr[low] < tmp&&low < high)
{
low++;
}
if (arr[low] > tmp)
{
arr[high] = arr[low];
}
else
{
break;
}
}
arr[low] = tmp;
return low;//low代表找到的基准的位置
}
递归实现快速排序
void Quick(int *arr, int start, int end)//递归
{
int par = Partion(arr, start, end);//par代表找到的基准位置
if (par > start + 1)//保证左边至少有两个数字
{
Quick(arr, start, par - 1);
}
if (par < end - 1)//保证右边至少有两个数字
{
Quick(arr, par + 1, end);
}
}
非递归实现快速排序
利用栈来实现
void quicksort(int *arr, int len)//非递归
{
int tmpsize = log((double)len) / log((double)2);
int *stack = (int *)malloc(sizeof(int)*tmpsize * 2);//动态申请一个栈,用来存放下标
assert(stack != NULL);
int top = 0;//数组的下标
int low = 0;
int high = len - 1;
int par = Partion(arr, low, high);//第一次的基准位置
if (par > low + 1)//当左边至少有两个数字时,左边下标入栈
{
stack[top++] = low++;
stack[top++] = par - 1;
}
if (par < high - 1)//当右边至少有两个数字时,右边下标入栈
{
stack[top++] = par + 1;
stack[top++] = high--;
}
while (top > 0)//栈不为空
{
high = stack[--top];//出栈
low = stack[--top];
par = Partion(arr, low, high);//再次找基准并进行排序
if (par > low + 1)//再次判断找到的基准
{
stack[top++] = low++;
stack[top++] = par - 1;
}
if (par < high - 1)
{
stack[top++] = par + 1;
stack[top++] = high--;
}
}
free(stack);//当栈为空代表已经全部进行了排序
stack = NULL;
}
快速排序(递归):
好情况:O(
)
坏情况:O(
)
稳定性:不稳定
好情况:Partition 每次都划分得很均匀。
坏情况:当待排序数组是正序或者逆序的时候。 (退化成选择排序)
空间复杂度: O( )
随机选取基准法
void Quick(int *arr, int start, int end)//递归
{
srand((unsigned int)time(NULL));
Swap(arr, start, rand() % (end - start) + start);
int par = Partion(arr, start, end);//par代表找到的基准位置
if (par > start + 1)//保证左边至少有两个数字
{
Quick(arr, start, par - 1);
}
if (par < end - 1)//保证右边至少有两个数字
{
Quick(arr, par + 1, end);
}
}
三分取中法
将中位数作为第一次的基准 ,即把中位数交换到low的位置
void SelectPivotMedianOfThree(int *arr, int low, int high)
{
int mid = (high - low) / 2 + low;
if (arr[mid] > arr[low])
{
Swap(arr, mid, low);//arr[mid]<=arr[low]
}
if (arr[mid] > arr[high])
{
Swap(arr, mid, high);//arr[mid]<=arr[high]
}
if (arr[low] > arr[high])
{
Swap(arr, low, high);
}
}
快速排序的优化
(1)当待排序的区间中,待排序的个数小于某个数量级的时候,使用插入排序(插入排序越有序越快)
(2)聚集相同基准的元素
void FocusNumPar(int *arr, int low, int par, int high, int *left, int *right)
{
if (low < high)
{
int parLeft = par - 1;
for (int i = par - 1; i >= low; i--)
{
if (arr[i] == arr[par])
{
if (i != parLeft)
{
Swap(arr, i, parLeft);
parLeft--;
}
else
{
parLeft--;
}
}
}
*left = parLeft;
int parRight = par + 1;
for (int i = par + 1; i <= high; i++)
{
if (arr[i] == arr[par])
{
if (i != parRight)
{
Swap(arr, i, parRight);
parRight++;
}
else
{
parRight++;
}
}
}
*right = parRight;
}
}
堆排序
堆排序的思想:
- 调整函数:调整指的是从最后一棵枝丫开始,从上往下调整(一次
调整) - 然后建立大根堆,而在这个过程当中需要不断的进行调整。
一棵树的调整:
void Adjust(int *arr, int start, int end)//一棵树的调整
{
int tmp = arr[start];
for (int i = 2 * start + 1; i <= end; i = i * 2 + 1)
{
if (i < end&&arr[i] < arr[i + 1])//判断有没有右孩子 i保存孩子中值最大的下标
{
i++;
}
if (arr[i] > tmp)//将孩子结点中值最大的与父亲结点进行比较,保证父亲结点的值大于孩子结点的值
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = tmp;
}
void HeapSort(int *arr, int len)
{
for (int i = (len - 1 - 1) / 2; i >= 0; i--)//大根堆的调整过程,从最后一个非叶子结点开始
{
Adjust(arr, i, len - 1);//每个非叶子结点都要调用这个函数
}
for (int i = 0; i < len - 1; i++)//先交换,后调整
{
int tmp = 0;
tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
Adjust(arr, 0, len - 1 - i - 1);//每次只调整0号位置的元素
}
}
大根堆的建立过程:大根堆建立完后的步骤就是交换和调整,先交换后调整。每次只用调整0号下标的值。
堆排序:
好情况:O(
)
坏情况:O(
)
稳定性: 不稳定
空间复杂度:O(1)
二路归并排序
二路归并排序的思想:
即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并。
void Merge(int *arr, int len, int gap)//每组有序
{
int *brr = (int *)malloc(sizeof(int) * len);//动态申请内存空间用来保存排好序的数组
assert(brr != NULL);
int i = 0;//brr的下标
int start1 = 0;
int end1 = start1 + gap - 1;
int start2 = end1 + 1;
int end2 = start2 + gap - 1 < len - 1 ? start2 + gap - 1 : len - 1;
while (start2 < len) //当有两个归并段的时候
{
while (start1 <= end1 && start2 <= end2) //当两个归并段还没有比较完的时候
{
if (arr[start1] <= arr[start2])
{
brr[i++] = arr[start1++];
}
else
{
brr[i++] = arr[start2++];
}
}
while (start1 <= end1)
{
brr[i++] = arr[start1++];
}
while (start2 <= end2)
{
brr[i++] = arr[start2++];
}
start1 = end2 + 1;//找两个新的归并段
end1 = start1 + gap - 1;
start2 = end1 + 1;
end2 = start2 + gap - 1 < len - 1 ? start2 + gap - 1 : len - 1;
}
while (start1 < len)//只有一个归并段的时候
{
brr[i++] = arr[start1++];
}
for (int i = 0; i < len; i++)
{
arr[i] = brr[i];
}
void MergeSort(int *arr, int len)//二路归并排序
{
for (int i = 1; i < len; i *= 2)
{
Merge(arr, len, i);
}
}
时间复杂度:
好情况:O(
)
坏情况:O(
)
空间复杂度:O(n)
稳定性:稳定
基数排序
基数排序:又称“桶子法”排序,他是根据待排序的每一位上的数字进行入“桶”排序,桶的数量跟当前单个数字的取值范围有关。当前数字是十进制,单个数字就是 0-9.类似队列。
链表的实现:
typedef struct Node
{
int data;
struct Node *next;
}Node, *List;
void InitList(List plist)//初始化
{
assert(plist != NULL);
plist->next = NULL;
}
static Node *GetNode(int val)//得到一个结点
{
Node *pGet = (Node *)malloc(sizeof(Node));
assert(pGet != NULL);
pGet->data = val;
pGet->next = NULL;
return pGet;
}
void Insert(List plist, int val)//尾插法
{
Node *p = plist;
while (p->next != NULL)
{
p = p->next;
}
Node *pGet = GetNode(val);
p->next = pGet;
}
}
bool DeleteFirst(List plist, int *rtv)//删除第一个结点
{
assert(plist != NULL);
Node *pDel = plist->next;
if (pDel == NULL)
{
return false;
}
*rtv = pDel->data;
plist->next = pDel->next;
free(pDel);
pDel = NULL;
return true;
}
基数排序代码实现
int GetMaxBit(int *arr, int len)//找出数组的最大值,算出最大值的位数
{
int max = arr[0];
int count = 0;
for (int i = 1; i < len; i++)
{
if (arr[i] > max)
{
max = arr[i];
}
}
while (max != 0)
{
count++;
max /= 10;
}
return count;
}
int GetNum(int num, int figures)//得到第figures位的数字
{
for (int i = 0; i < figures; i++)
{
num /= 10;
}
num = num % 10;
return num;
}
void Radix(int *arr, int len, int figures)
{
Node head[10];
for (int i = 0; i < 10; i++)
{
InitList(&head[i]);//初始化
}
//1.入桶==>拿到数字判断第figures位为数字多少,入相应的桶里
int tmp = 0;
int i = 0;
for (; i < len; i++)
{
tmp = GetNum(arr[i], figures);
Insert(&head[tmp], arr[i]);
}
//2.出桶
i = 0;
for (int j = 0; j < 10; j++)//j代表桶的下标,一个桶出完再出另外一个桶
{
while (DeleteFirst(&head[j], &arr[i]))
{
i++;
}
void RadixSort(int *arr, int len)//基数排序(桶排序)(只考虑正数)
{
assert(arr != NULL);
int count = GetMaxBit(arr, len);//确定入桶的次数
for (int i = 0; i < count; i++)
{
Radix(arr, len, i);//i==>从右往左数第figures位的数字
}
}
}
}
时间复杂度:O( )
r是趟数,n是数组的个数。