概述
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
我们这里说说八大排序就是内部排序。
1.选择排序(Simple Selection Sort)
基本思想:选择排序是一种简单直观的排序算法。,它是一个时间复杂度为O(n^2),空间复杂度为O(1)的排序,并且是不稳定的。核心在比较。
代码如下:
void SelectSort(int *arr,int len)
{
int i = 0;
int tmp = 0;
while (i < len)
{
int j = i+1;
while (j < len)
{
if (arr[i] <= arr[j])
{
j++;
}
else
{
tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
j++;
}
}
i++;
}
}
2.冒泡排序(Bubble Sort)
基本思想:冒泡排序的时间复杂度为O(n^2),在有序下为O(n)。空间复杂度为O(1)的排序,并且是稳定的。冒泡排序通过依次交换相邻两个数的位置,从而避免了出现相同数的位置被调换的情况,因此是稳定的。
代码如下:
void BubbleSort(int *arr,int len)
{
int i = 0;
int tmp = 0;
while (i < len - 1)
{
int j = i;
while (j < len-1-i)
{
if (arr[j] <= arr[j + 1])
{
j++;
}
else
{
tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
j++;
}
}
i++;
}
}
3.直接插入排序(Straight Insertion Sort)
基本思想:直接插入排序是一个时间复杂度为O(n^2),在有序下为O(n)。空间复杂度为O(1)的排序,并且是稳定的。通过一个临时数,然后定义两个指向数组的下标i和j,i下标里面的内容先存放再tmp中,通过比较该下标之前的下标j的数是否大于该tmp的值,如果是,则进行交换。如果向前检索到一个小于tmp值的数时,则停止检索之前的数,因为通过此方法所进行的交换,之前小于i下标里存的值的数已经全部小于该数,因此该情况也是稳定的。
代码如下:
void InsertSort(int *arr, int len)
{
int tmp = 0;
int j = 0;
for (int i=1;i < len;i++)
{
tmp = arr[i];
for (j=i-1;j >= 0;j--)
{
if (arr[j] >= tmp)
{
arr[j + 1] = arr[j];
}
else
{
break;
}
}
arr[j + 1] = tmp;
}
}
4.希尔排序(Shell`s Sort)
基本思想:希尔排序是一个时间复杂度为O(n^1.3)到 O(n^1.5),空间复杂度为O(1)的排序,并且是不稳定的。希尔排序是将数组中的元素进行等额划分,一般划分为奇数。先进行五组等分,然后依次对每一组里的第二个数进行排序交换,当交换完之后接着换为第二组的第二个数存到tmp中,与第二个tmp之前的数进行比较。如此下去,知道所有的组遍历完。
代码如下:
void Shell(int *arr, int len,int gap)
{
int tmp = 0;
int j = 0;
for (int i=gap;i < len;i++)
{
tmp = arr[i];
for (j =i-gap;j >= 0;j = j-gap)
{
if (arr[j] <= tmp)
{
break;
}
else
{
arr[j + gap] = arr[j];
}
}
arr[j + gap] = tmp;
}
}
void ShellSort(int *arr, int len)
{
int drr[] = { 5,3,1 };
int lend = sizeof(drr) / sizeof(drr[0]);
for (int i=0;i < lend;i++)
{
Shell(arr,len,drr[i]);
}
}
5.快速排序(Quick Sort)
基本思想:快速排序是一个在最坏情况下时间复杂度为O(n^2),最好情况下为O(nlog2n)。空间复杂度为O(log2n)的排序,并且是不稳定的。快速排序分为递归排序和非递归排序。先说递归排序,先在数组里找一个基准,然后以此基准对基准的左右分别和基准进行比较,一趟完了之后,然后通过递归从而得到有序。然而找基准的时候又有几种方式,有三分取中法和利用随机数取数组中的随机下标进行排序。在非递归快速排序中:通过先申请一个栈,然后将基准左右左右两边进行交换的一段数组的起始下标和终点下边存入在栈中,最后依次输出栈中的内容。
代码如下:
void QuickSort1(int *arr, int len)
{
int tmpSize = (int)ceil(log((double)len));
int *stack = (int *)malloc(sizeof(int)*tmpSize*2);
assert(stack != NULL);
int low = 0;
int high = len - 1;
int par = Partion(arr,0,len-1);
int p = 0;
if (par > low+1)
{
stack[p++] = low;
stack[p++] = par - 1;
}
if (par < high-1)
{
stack[p++] = par+1;
stack[p++] = high;
}
while (p > 0)
{
high = stack[--p];
low = stack[--p];
par = Partion(arr,low,high);
if (par > low + 1)
{
stack[p++] = low;
stack[p++] = par - 1;
}
if (par < high - 1)
{
stack[p++] = par + 1;
stack[p++] = high;
}
}
}
int Partion(int *arr, int low, int high)
{
int tmp = 0;
int l = low;
int h = high;
tmp = arr[low];
while (l != h)
{
while (arr[h] >= tmp && h > l)
{
h--;
}
if (h < l)
{
break;
}
else
{
arr[l] = arr[h];
}
while (arr[l] <= tmp && l < h)
{
l++;
}
if (l > h)
{
break;
}
else
{
arr[h] = arr[l];
}
}
arr[l] = tmp;
return l;
}
void Swap(int *arr,int low,int high)
{
int tmp = 0;
tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
void Median_Of_Three(int *arr,int low,int mid,int high) //三分取中法
{
if (arr[mid] > arr[low])
{
Swap(arr,mid,low);
}
if (arr[mid] > arr[high])
{
Swap(arr, mid, high);
}
if (arr[low] > arr[high])
{
Swap(arr, low,high);
}
}
void Focus_Num(int *arr,int low,int par,int high,int *left,int *right) //聚集法
{
int tmp = 0;
int pr = par+1;
int i = par + 1;
while (i <= high) //先对基准的右半边进行基准聚集
{
if (arr[i] == arr[par]) //判断紧挨基准的右边的值是否相等
{
if (i != pr)
{
Swap(arr,i,pr); //交换与基准相等的下标
pr++;
}
else
{
pr++;
}
}
*right = pr;
i++;
}
int pl = par - 1;
for (int j = low;j < pl;j++) //对基准左边的进行交换
{
if (arr[i] == arr[par])
{
Swap(arr,i,pl);
pl--;
}
*left = pl;
}
}
void Quick(int *arr, int low, int high)
{
/*if (high - low < 100)
{
InsertSort(arr,high-low);
return;
}*/
/*srand(time(0));
Swap(arr,low,rand()%(high-low)+low);*/
Median_Of_Three(arr,low,(high-low)/2+low,high);//三分取中法
int par = Partion(arr,low,high);
int left = par - 1;
int right = par + 1;
Focus_Num(arr,low,par,high,&left,&right); //将与基准相同的集中再基准左右两边
if (par > low+1)
{
Quick(arr,low,par-1);
}
if (par < high-1)
{
Quick(arr, par+1, high);
}
}
void QuickSort(int *arr, int len)
{
Quick(arr,0,len-1);
}
6.堆排序(Heap Sort)
基本思想:堆排序是一个时间复杂度为O(nlogn),空间复杂度为O(1)的排序,并且是不稳定的。堆排序是通过先将数组中的元素进行大根堆排序,即呈一个倒立的树状,每个节点的关键字不能小于其孩子节点的关键字,在建立大根堆的时候当选取一个开始节点时判断该节点的值是否大于其孩子节点的值,此时还需要判断其是否有孩子节点并且其孩子节点时候还有子节点也要进行判断。大根堆建立好之后,通过将第一个节点的值和最后一个节点的值进行交换,依次下去,从而得到小根堆。即为有序的。
代码如下:
void Adjust(int *arr,int start,int end)
{
int tmp = arr[start];
for (int i=2*start+1;i < end;i = 2*i+1)
{
if (i < end && arr[i] < arr[i+1])
{
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);
}
int tmp = 0;
for(int i = 0;i < len - 1;i++)
{
tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
Adjust(arr, 0, len - 1-i-1);
}
}
7.归并排序(Merge Sort)
基本思想:归并排序是一个时间复杂度为O(nlogn),空间复杂度为O(n)的排序,并且是稳定的,但很消耗空间。先申请一个数组,然后定义四个变量分别指向两个子数组,需要将子数组进行偶数倍的划分。当两个子数组中只剩下一个元素。并且右边的数组需要进行判断不能越界,在循环里找符合交换的元素进行交换,最后再对未交换的元素依次输出,将其存入在申请的临时数组中。接着将变量指向下一块分区的子数组下标,然后接着去循环。最后再输出临时数组中的元素。
代码如下:
void Merge(int *arr,int len,int gap)
{
int *brr = (int *)malloc(sizeof(int)*len); //申请一个数组
//assert(brr != NULL);
int i = 0;
int s1 = 0;
int e1 = s1+gap-1;
int s2 = e1+1;
int e2 = s2+gap-1 < len-1 ? s2 + gap - 1:len-1;//右边不能越界
while (s2 < len)
{
while (s1 <= e1 && s2 <= e2)
{
if (arr[s1] > arr[s2]) //如果s1>s2则交换两个下标对应的值
{
brr[i++] = arr[s2++];
}
else
{
brr[i++] = arr[s1++]; //否则下标向后移动
}
}
while (s1 <= e1) //对s1里面未交换的进行依次输出
{
brr[i++] = arr[s1++];
}
while (s2 <= e2)
{
brr[i++] = arr[s2++];
}
s1 = e1 + 1; //移动下标到后一个相同长度的位置
e1 = s1 + gap - 1;
s2 = e1 + 1;
e2 = s2 + gap - 1 > len - 1 ? s2 + gap - 1 : len - 1;
}
while (s1 < len)
{
brr[i++] = arr[s1++];
}
for (int i=0;i < len;i++)
{
arr[i] = brr[i]; //输出存入数组中的内容
}
free(brr);
brr = NULL;
}
void MergeSort(int *arr,int len)
{
for (int i=1;i < len;i *= 2)
{
Merge(arr,len,i); //利用偶数倍进行归并
}
}
8.基数排序(Radix Sort)
基本思想:基数排序是一个时间复杂度为O(d*(r+n)),d为次数,r为桶数,空间复杂度为O(n)的排序,并且是稳定的。基数排序是通过申请十个链表分别代表0到9十个数,然后对数组中的元素中的每一位进行判断大小而进入不同的链表中,位数是从右到左进行依次判断,最后读取不同的位数多次插入输出这些链表。即可得到排好序的数组。需要建立单链表以进行申请单链表,在进行判断位数的同时,需要先判断数组中最大的元素的位数是几位才能判断总共插入和输出几次。
代码如下:
typedef struct Node
{
int data;
struct Node *next;
}Node, *List;
void InitList(List plist)
{
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;
}
bool Insert(List plist, int val)
{
Node *p = plist;
while (p->next != NULL)
{
p = p->next;
}
Node *pGet = GetNode(val);
p->next = pGet;
return true;
}
bool DelFirst(List plist, int *rtv)
{
Node *p = plist->next;
if (p == NULL)
{
return false;
}
*rtv = p->data;
plist->next = p->next;
free(p);
p = NULL;
return true;
}
int GetBitMax(int *arr, int len)
{
int i = 0;
int max = 0;
while(i <= len)
{
if (arr[i] > max)
{
max = arr[i];//先得到数组中的最大数
}
i++;
}
int count = 0;
while (max != 0)
{
max = max / 10;
count++;
}
return count; //返回最大数的位数
}
int GetNum(int num, int figure)
{
for (int i = 0;i < figure;i++)
{
num /= 10;
}
return num % 10;
}
void Base(int *arr, int len, int figure)
{
Node head[10]; //申请十个单链表
for (int i = 0;i < 10;i++)
{
InitList(&head[i]);
}
int tmp = 0;
for (int i = 0;i < len;i++)
{
tmp = GetNum(arr[i], figure); //获取数组中每一个元素所对应位置的数
Insert(&head[tmp],arr[i]); //根据获取到的数分别插入到相应的链表中
}
int m = 0;
for (int j=0;j < 10;)
{
if (DelFirst(&head[j], &arr[m])) //利用循环依次输出10个链表中的元素
{
m++;
}
else
{
j++;
}
}
}
void BaseSort(int *arr,int len)
{
int count = GetBitMax(arr, len);//得到当前数组中最大的数时几位数,记录位数
for (int i = 0;i < count;i++)
{
Base(arr, len, i);//根据位数分别对其进行排序
}
}