排序算法主要分为三大类,分别是插入排序、选择排序和交换排序。其中插入排序包括直接插入排序和希尔排序;选择排序包括直接选择排序和堆排序;交换排序包括冒泡排序、快速排序和归并排序。
把待排序的数据插入到已经排好序的数据中,直到所有的数据插入完成,即就是直接插入排序。一般待排序的区间是第二个数到最后一个数,把第一个数当作已经排序的数据。
void Insert_Sort(int arr[], int size)
{
assert(arr);
int i; //待插入元素的下标
int j; //需要进行比较的元素的下标。在i前面的元素都是已经排序好的元素。
int temp;
for (i = 1; i < size; ++i) //从第二个元素插入,保证有可以比较的元素。
{
temp = arr[i]; //需要插入的元素,进行保存。
for (j = i - 1; j >= 0; --j)//在已经有序的数列中,进行比较。
{
if (temp < arr[j]) //进行升序排序。
arr[j + 1] = arr[j];
else //如果带插入元素不大于前一个元素,就停止
break;
}
arr[j + 1] = temp; //即为被插入元素的位置
}
}
将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。是对直接插入排序算法的优化。第一次的间隔是数据长度的三分之一再加一,即 gap = size / 3 + 1。
void InsertSortWithgap(int arr[], int size, int gap)
{
assert(arr);
int i;
int j;
int temp;
for (i = gap; i < size; ++i) //从下标为gap的元素作为带插入元素
{
temp = arr[i]; //存储gap下标的元素
for (j = i - gap; j >= 0; j -= gap) //从第0个元素进行比较
{
if (temp > arr[j]) //降序
arr[j + gap] = arr[j]; //gap位置的元素换成j所在下标的元素
else
break;
}
arr[j + gap] = temp;
}
}
//希尔
void ShellSort(int arr[], int size)
{
int gap = size;
while (1)
{
gap = gap / 3 + 1;
InsertSortWithgap(arr,size,gap);
if (gap == 1) break;
}
}
选择排序就是在待排序的数据中选择一个最大的或者最小的放在带待排序数据的末尾。
//选择排序
void Choice_Sort(int arr[], int size)
{
for (int i = 1; i < size; ++i)
{
int max = 0; //假设最大的数的下标是第 0 个
for (int j = 1; j <= size - i; ++j)
{
if (arr[j] > arr[max])
max = j;
}
int temp = arr[size - i];
arr[size - i] = arr[max];
arr[max] = temp;
}
}
堆的作用就是寻找最值。所以根节点肯定最大(大堆)或者最小(小堆)。我们把根节点和最后一个结点进行交换,数组最后一个数肯定就是最大或者最小的。最后又在剩下的数据里面建堆。 要注意的是升序建大堆,降序建小堆。
堆排序可以按照以下步骤来完成:
1、首先将序列构建称为大顶堆。
2、取出当前大顶堆的根节点,将其与序列末尾元素进行交换,此时序列末尾的元素为已排序的最大值。
3、对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质。
4、重复2和3步骤,直至堆中只有1个元素为止。
void Adjust_down(int arr[], int size, int root)
{
while (1)
{
int left = root * 2 + 1;
int right = root * 2 + 2;
int min = left; //最小的为最左孩子
if (left >= size) //没有孩子结点
return;
if (right < size && arr[right] < arr[left]) //有右孩子
min = right;
if (arr[root] <= arr[min])//根节点已经是最小
return;
int temp = arr[root];
arr[root] = arr[min];
arr[min] = temp;
root = min;
}
}
//建堆
void CreateBigHeap(int arr[], int size)
{
//对左子树建堆,再对右子树建堆
int notlevel = (size - 2) / 2; //从最后一个非叶子结点开始调整
while (notlevel >= 0)
{
Adjust_down(arr, size, notlevel);
--notlevel;
}
}
void HeapSort(int arr[], int size)
{
//建堆
CreateBigHeap(arr, size);
for (int temp = 1; temp < size; ++temp)
{
int t = arr[0];
arr[0] = arr[size - temp]; //这个值就是最后一个叶子结点。
arr[size - temp] = t;
Adjust_down(arr, size-temp,0); //从根节点开始调整
}
}
从第一个数开始,和第二个数比较,满足条件就进行交换,然后第二个数和第三个数进行比较满足进行交换,直到最后一个数,这是一个“泡”已经冒出。现在有从开始比较,这个时候总的比较的数减少一个,因为“泡”已经冒出了。冒泡排序一共会进行 size - 1次冒泡,每次的比较次数为size - i,i是比较的第几次。
冒泡排序思路:
1、将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素。第一轮结束后,序列最后一个元素一定是当前序列的最大值。
2、对序列当中剩下的n-1个元素再次执行步骤1。
3、对于长度为n的序列,一共需要执行n-1轮比较。
void Bubble_Sort(int arr[], int size)
{
assert(arr);
for(int row = 0; row < size - 1; ++row)
{
int flag = 1;
for(int col = 0; col < size - row - 1; ++col)
{
if (arr[col] < arr[col + 1])
{
int temp = arr[col + 1];
arr[col + 1] = arr[col];
arr[col] = temp;
flag = 0;
}
}
if (flag == 1) break;
}
}
1、选择一个基准值,这个基准值一般选取最左边或最右边。
2、遍历整个区间,把除基准值之外的所有数据与基准值进行比较,并进行数据移动,满足比基准值小的数放在基准值的左边,比基准值大的数放在基准值的右边。
3、分治算法,利用递归,让基准值的左右边的区间也重复上面的操作,直到区间的size为 0 或者 1。
int partition(int arr[], int left, int right)
{
int begin = left; //left == 数组的第一个元素的下标。并不一定是 0.比如在基准值右边的区间
int end = right; //right == 数组的最后一个元素的下标
while (begin < end) //当两个元素不相遇的时候。
{
while (begin < end && arr[begin] <= arr[right])
{
++begin; //满足条件比较下一个元素,向右移动
}
while (begin < end && arr[end] >= arr[right])
{
--end; //向左移动
}
int temp = arr[begin]; //当begin和end没有相遇,且不满足条件时。交换两个元素
arr[begin] = arr[end];
arr[end] = temp;
}
int temp = arr[begin];
arr[begin] = arr[right];
arr[right] = temp; //最后把基准值填在中间
return begin; //返回基准值的下标
}
void QuickSort(int arr[], int left, int right)
{
if (left >= right) return;
int povit = partition(arr, left, right);
QuickSort(arr, left, povit - 1);
QuickSort(arr, povit + 1, right);
}
void Quick_Sort(int arr[], int size)
{
QuickSort(arr, 0 , size -1);
}
归并排序也需要借助分治算法。但是这个的基准值是一直是数组的中间那个数,而不是像快速排序一样选边上的数据作为基准值。归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
}