排序算法(笔记)

本文详细介绍了插入排序、希尔排序、堆排序、直接选择排序、交换排序(包括冒泡排序和快速排序)、以及归并排序的原理、实现代码和特性,包括时间复杂度、空间复杂度和稳定性分析。此外,还提到了快速排序的优化策略——三数取中,以及归并排序的非递归实现。
摘要由CSDN通过智能技术生成

1.插入排序

       基本思想:直接插入排序是一种简单的插入排序法,其基本思想是把待排序的记录按其关键码值的大小,逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列,实际中我们玩扑克牌时就用了插入排序的思想。
①直接插入排序:当插入第i(i>=1)个元素时,前面的array[0],array[1],...,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2]的排序码顺序进行比较,找到插入位置,即将array[i]插入,原来位置上的元素顺序后移。

插入排序的时间复杂度:O(n²),逆序时最坏(1+2+3+···n=(n*(n-1)/2),顺序时最好,空间复杂度:O(1),是一种稳定的排序算法。

#include<stdio.h>
void PrintArray(int* a,int n)
{
    for(int i=0;i<n;i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
}
//插入排序
void InsertSort(int *a, int n)
{
   //[0,end]有序,end+1的位置插入进去,让[0,end+1]有序
   for(int i=0;i<n-1;++i)
   {
   int end=i;//end值会变
   int tmp=a[end+1];//把后一个值保存起来,因为会覆盖
   while(end>=0)
   {
       if(a[end]>tmp)
       {
           a[end+1]=a[end];//end的值比end+1大end移到end+1的位置
           --end;//与end前面的数进行比较
       }
       else {
           break;
       }
   }
   a[end+1]=tmp;//找到,把tmp放到end后面的位置即end+1,或者是i=0,tmp插入到a[0]前面,如果这句话放在else里,则不能实现i=0时,tmp插入到a[0]前面    
   }
}
void TestInsertSort()
{
    int a[]={3,5,2,7,8,6,1,9,4,0};
    InsertSort(a,sizeof(a)/sizeof(int));
    PrintArray(a,sizeof(a)/sizeof(int));
}
int main()
{
    TestInsertSort();
    return 0;
}

2.希尔排序 (直接插入排序的基础上优化)

       希尔排序又称缩小增量法,希尔排序的基本思想:先选定一个整数,把待排序文件中所有记录分成几个组,所有距离相同的记录分在一组内,并对每一组内的记录进行排序。然后,取重复上述分组和排序的工作。当到达距离=1时,所有记录在统一组内排好序。

1.先进行预排序(分组排,间隔为gap是一组,假设gap==3),让数组接近有序;

2.直接插入排序

       如下图,是对组间隔为gap的预排序,gap由大变小。gap越大,大的数可以越快的到后面小的数可以越快的到前面;gap越大,预排完越不接近有序,gap越小,越接近有序。gap==1时是直接插入排序。

//时间复杂度:O(logN*N)或者O(log3N*N)
//平均时间复杂度:O(N^1.3)
void ShellSort(int*a,int n)
{   int gap=n;
    while(gap>1)
    {
    gap=gap/2;
    //gap=gap/3+1
    //gap>1时是预排序
    //gap==1时是直接插入排序
    //gap很大时,下面预排序的时间复杂度为O(n)
    //gap很小时,数组已经很接近有序了,这时差不多也是O(n)
    //把间隔为gap的多组数据同时排
    for(int i=0;i<n-gap;++i)
    {
    int end;
    int tmp=a[end+gap];
    while(end>=0)
    {
        if(a[end]>tmp)
        {
           a[end+gap]=a[end];//end的值比end+1大end移到end+1的位置
           end-=gap;//与end前面的数进行比较
        }
        else 
        {
           break;
        }
           a[end+gap]=tmp;
    } 
    }   
    }   
}

3.堆排序

堆的两个特性:

结构性--用数组表示的完全二叉树;

有序性--任一结点的关键字是其子树所有结点的最大值(或最小值)。

堆的逻辑结构是一棵完全二叉树,物理结构是一个数组,通过下标父子结点关系--leftchild=  parent*2+1;rightchild=parent*2+2;parent=(child-1)/2。child是左孩子或者右孩子

大堆要求:树中所有的父亲都大于等于孩子。

小堆要求:树中所有的父亲都小于等于孩子。

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的
一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

向下调整算法:前提--左右子树都是小堆。如下图,从根节点开始,选出左右孩子中小的那一个,跟父亲比较,如果比父亲小就交换,然后再继续往下调,调到叶子结点就终止。如果左右子树不是小堆,倒着从最后一棵子树开始调,叶子结点不需要调,从倒数最后一个非叶子的子树(最后一个(下标n-1)叶子结点的父亲结点(n-1-1)/2)开始调。

for (int i=(n-1-1)/2;i>=0;--i)
{
   AdjustDown(a,n,i);
}
向下调整算法
void AdjiustDown(int *a,int n,int root)
{
    int parent=root;//父亲节点
    int child=parent*2+1;//默认是左孩子
    while(child<n)//没有超出数组范围
    {
        //选出左右孩子中大的那一个
        if(child+1<n&&a[child+1]<a[child])//只有一个孩子结点或者右孩子小于左孩子
        {
            child+=1;//child变成右孩子
        }
        if(a[child]<a[parent])//孩子结点比父亲结点要小
        {
            Swap(&a[child],&a[parent]);
            parent=child;//父亲节点往下走
            child=parent*2+1;//孩子结点也往下走
        }
        else 
        {
            break;
        }//较小的孩子结点比父亲要大
    }
}

堆排序的特性总结:
1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

选择排序--通过堆来选数,如果是建小堆,最小数在堆顶,已经被选出来了。那么在剩下的数中再去选数,但是剩下树结构都乱了,需要重新建堆,才能选出下一个数,建堆的时间复杂度是O(n),这样不是不可以,但是堆排序就没有效率优势了。

void Heapsort(int *a,int n)
{
     //建堆
    for(int i=(n-1-1)/2;i>=0;--i)
    {
      AdjustDown(a,n,i);
    } 
    //排升序,建大堆
    int end=n-1;//最后一个叶子结点
    while(end>0)
    {
      Swap(&a[0],&a[end]);//第一个数和最后一个交换,最大数放在最后,把它不看做堆里面
      AdjustDown(a,end,0);//前n-1个数向下调整,选出次大的数,再跟倒数第二个位置交换
      --end;
    }
}

4.直接选择排序

选择排序的基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

直接选择排序
①在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素;
②若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)
元素交换;
③在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩
余1个元素。

直接选择排序的特性总结
1. 直接选择排序思考非常好理解,但是效率不是很好,实际中很少使用;
2. 时间复杂度:O(N^2);
3. 空间复杂度:O(1);
4. 稳定性:不稳定 。

//直接选择排序,时间复杂度O(N*N)
//很差,因为最好情况也是O(N*N)
void SelectSort(int *a,int n)
{
    int begin=0,end=n-1;   
    while(begin<end)
    { 
      int mini=begin,maxi=begin;
      //第一次begin=a[0],end=a[n-1],找出序列中的最大值和最小值
      for(int i=begin;i<=end;i++)
      {
         if(a[i]<a[mini])
         {
             mini=i;
         }
         if(a[i]>a[maxi])
         {
             maxi=i;
         }
      }
      Swap(&a[begin],&a[mini]);
      if(begin==maxi)
      {
         maxi=mini;
      }//begin=maxi时要对maxi的值进行修正
      Swap(&a[end],&a[maxi]);//将找到的最小值放在a[0]最大值放在a[n-1]
      ++begin;
      --end;//将begin移到a[1],end移到a[n-2],再从begin到end中寻找最大和最小值,重复上述操作
    }
}
begin==maxi的情况

void Selectionsort(int a[],int n)
{
    int t,flag;
    for(int i=0;i<n-1;i++)
    {
        flag=i;
        for(int j=i+1;j<n;j++)
        {
            if(a[flag]>a[j])
            flag=j;
        }
        t=a[i];
        a[i]=a[flag];
        a[flag]=t;
    }
}//简易版

5.交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位
置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移
动。 

5.1冒泡排序

//冒泡排序,时间复杂度O(N*N)
//最好情况O(n)
void BulleSort(int* a,int n)
{
    for(int j=0;j<n;j++)
  {
    int exchange=0;
    for(int i=1;i<n-j;i++)
    {
       if(a[i-1]>a[i])
       {
          Swap(&a[i-1],&a[i]);
          exchange=1;
       }
    }
    if(exchange==0)
    {
       break;//这一趟没有交换顺序,提前结束
    }
  }
}

冒泡排序的特性总结
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定 

5.2快速排序

基本思想:任取待排序元素序列中的某元素作为基准值(一般是第一个或者最后一个),按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。左边有坑,右边找小;右边有坑,左边找大。

快速排序的特性总结: 

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)
4. 稳定性:不稳定

void QuickSort(int*a,int left,int right)
{
   if(left>=right)
  {
    return;//递归的子区间不存在
  }
   int begin=left,end=right-1;
   int pivot=begin;//第一个元素作为关键字后,第一个元素变成坑
   int key=a[begin];//数组的第一个元素作为关键字
 while(begin<end)
{
   //右边的数从end往前看,如果比key小,放到左边那个坑,并在end处形成新的坑,此时的end为刚才比key小的那个元素的位置
   while(begin<end&&a[end]>=key)
  { 
   --end;//左边有坑,右边找小,放到左边,右边的数比key大,往前查找,直到找到比key小的
  }
   a[pivot]=a[end];//小的放到左边的坑里,自己形成新的坑位
  //右边有坑,找大,从begin往后看,如果找到比key大的元素,放到右边那个坑,并在begin处形成一个新的坑,此时的begin为刚才比key大的那个元素的位置
   while(begin<end&&a[end]<=key)
  { 
   ++begin;//右边有坑,左边找大,放到右边,左边的数比key小,往后查找,直到找到比key大的
  }
   a[pivot]=a[begin];//大的放到右边的坑里,自己形成新的坑位
   pivot=begin;
  }
   pivot=begin;
   a[pivot]=key;//最后把关键字放回坑里
   //初始的区间[left,right]在经过一次递归后变为[left,pivot-1]pivot[pivot+1,right]
   //左子区间和右子区间有序,就有序,方法--分治递归
   QuickSort(a,left,pivot-1);
   QuickSort(a,lpivot+1,right);
}
void TestQuickSort()
{
   int a[]={6,3,5,2,7,8,9,4,1};
   QuickSort(a,0,sizeof(a)/sizeof(int)-1);
   PrintArray(a,sizeof(a)/sizeof(int));
}

加入三数取中后时间变短了,解决了快排最坏的情况。      

// 三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;//中间值 
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
// 挖坑法
int PartSort2(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);

	int begin = left, end = right;
	int keyi = begin;

	while (begin < end)
	{
		// 找小
		while (begin < end && a[end] >= a[keyi])
		{
			--end;
		}

		// 找大
		while (begin < end && a[begin] <= a[keyi])
		{
			++begin;
		}

		Swap(&a[begin], &a[end]);
	}

	Swap(&a[begin], &a[keyi]);

	return begin;
}

// 左右指针法
int PartSort2(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);//首元素放的是原来位于中间的数 

	int begin = left, end = right;
	int keyi = begin;

	while (begin < end)
	{
		// 找小
		while (begin < end && a[end] >= a[keyi])
		{
			--end;
		}

		// 找大
		while (begin < end && a[begin] <= a[keyi])
		{
			++begin;
		}

		Swap(&a[begin], &a[end]);//end找小,begin找大,交换 
	}

	Swap(&a[begin], &a[keyi]);//相遇之后跟keyi交换 

	return begin;
}

int PartSort3(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);

	int keyi = left;//设关键字为第一个元素的值 
	int prev = left, cur = left + 1;
	while (cur <= right)//向右查找 
	{
		if (a[cur] < a[keyi] 
			&& ++prev != cur)//如果cur的值比keyi小且++prev==cur,元素不做改变,++cur,向后查找 
		{
			Swap(&a[prev], &a[cur]);
		}

		++cur;
	}
	Swap(&a[keyi], &a[prev]);//交换keyi和prev处的值 
	return prev;
}

6.归并排序

       归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法
(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序
列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二
路归并。 归并排序核心步骤如下:

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) >> 1;
	// 假设 [left, mid] [mid+1, right]有序,那么我们就可以归并了
	_MergeSort(a, left, mid, tmp);//让左边有序 
	_MergeSort(a, mid+1, right, tmp);//让右边有序 
	// 归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;//两个归并区间 
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)//有一个到尾结束就结束了 
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];//也就是从1,6,7,10,2,3,4,9到1,2,3,4,6,7,9,10的排序 
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];//begin1的没结束,把 begin1的数据全部放入tmp 
	}

	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];//begin2的没结束,把 begin2的数据全部放入tmp 
	}
	
	// 拷贝回去
	for (int i = left; i <= right; ++i)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

递归的缺陷:在极端情况下会导致栈溢出。递归深度太深,程序是没错的,但是栈的空间不够用。

递归改非递归:1.直接改循环;2.借助数据结构栈模拟递归过程。下面是栈模拟递归的过程:

//Stack.h
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
typedef int STDataType;

//#define MAX 10000
//struct Stack
//{
//	STDataType a[MAX];
//	int top;
//};

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void StackInit(ST* ps);
void StackDestory(ST* ps);
// 入栈
void StackPush(ST* ps, STDataType x);
// 出栈
void StackPop(ST* ps);
STDataType StackTop(ST* ps);

int StackSize(ST* ps);
bool StackEmpty(ST* ps);
//Stack.c
#include "Stack.h"
void StackInit(ST* ps)
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	ps->capacity = 4;
	ps->top = 0;
}
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
// 入栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	// 满了-》增容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}

	ps->a[ps->top] = x;
	ps->top++;
}
// 出栈
void StackPop(ST* ps)
{
	assert(ps);
	// 栈空了,调用Pop,直接中止程序报错
	assert(ps->top > 0);

	//ps->a[ps->top - 1] = 0;
	ps->top--;
}
STDataType StackTop(ST* ps)
{
	assert(ps);
	// 栈空了,调用Top,直接中止程序报错
	assert(ps->top > 0);

	return ps->a[ps->top - 1];
}
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}
bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}
void QuickSortNonR(int *a,int n)
{
    ST st;
    StackInit(&st);//每次调用栈时都要初始化
    StackPush(&st,n-1);
    StackPush(&st,0);//先进后出,所以先入右边的n-1,再入左边的0
    while(!StackEmpty(&st))//栈不为空就继续
    {
       int left=StackTop(&st);
       StackPop(&st);//左区间
       int right=StackTop(&st);
       StackPop(&st);//右区间
       int keyIndex=PartSort1(a,left,right);//对这一部分进行单趟排
       //分成三段区间[left,keyIndex-1]keyIndex[keyIndex+1,right]
    }
    //左区间只有一个数不需要排,也不需要入栈
    if(keyIndex+1<right)//先排左边的,就先入右边的,keyIndex+1<right说明右边不止一个数
    {
       StackPush(&st,right);
       StackPush(&st,keyIndex+1);
    }
    if(left<keyIndex-1)//left<keyIndex-1说明左边不止一个数
    {
       StackPush(&st,keyIndex-1);
       StackPush(&st,left);
    }
    StackDestory(&st);
    
}

函数调用建立栈帧,栈帧中存储局部变量参数等。

// 时间复杂度都是O(N*logN)
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	int gap = 1; // 每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i, i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			// 归并过程中右半区间可能就不存在(数据个数不为2的整数倍)
			if (begin2 >= n)
				break;
			// 归并过程中右半区间算多了, 修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			// 拷贝回去
			for (int j = i;j <=end2; ++j)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

归并排序,也叫外排序,还可以对文件中数据进行排序。如:10G的文件,切分成10个1G的文件,并且让10个1G的文件有序,一次读文件,每次读1G到内存中放到一个数组,用快排对其排序再写到一个文件,再继续读下一个1G的数据。如下图:

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记
录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍
在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。

计数排序

非比较排序
计数排序:思想很巧,适用范围具有局限性
时间复杂度:O(N+range),说明他适用于范围集中一组整形数据排序
空间复杂度:O(range)

//当数据值较大时,计数排序直接映射位置会浪费空间,因此要采用相对位置映射
//即用最大数减去最小数,用这一段的大小进行映射
void CountSort(int* a, int n)
{
   //先求出最大最小值
   int max=a[0],min=a[0];
   for(int i=0;i<n;++i)
  {
     if(a[i]>max)
    {
      max=a[i];
    }
     if(a[i]<min)
    {
      min=a[i];
    }
  }
   int range=max-min+1;//一共range个值
   int* count=(int*)malloc(sizeof(int)*range);//开辟range个空间,做映射
   memset(count,0,sizeof(int)*range);//将count里的值都初始化为0
   //统计次数
   for(int i=0;i<n;++i)
   {
      count[a[i]-min]++;//a[i]-min是相对值,即相对映射,节省了空间
   }
   int j=0;
   for(int i=0;i<range;++i)
   {
      while(count[i]--)//对于重复值,要写count[i]次
      {
         a[j++]=i+min;//返回原值
      }
   }
}

完整代码

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

// 假设都用升序来讲,降序就是反过来

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

// 插入排序
// 时间复杂度是多少?O(N^2)
// 什么情况下最坏?逆序  1+2+3+...+n-1
// 什么情况下最好?顺序有序  O(N)
void InsertSort(int* a, int n)
{
	// [0, end]有序  end+1位置的值插入[0, end],让[0, end+1]有序
	for (int i = 0; i < n-1; ++i)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

void TestInsertSort()
{
	int a[] = { 3, 5, 2, 7, 8, 6, 1, 9, 4, 0 };
	InsertSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

// 直接插入排序的基础上的优化
// 1、先进行预排序,让数组接近有序
// 2、直接插入排序
// 时间复杂度:O(logN*N) 或者 O(log3N*N)
// 平均的时间复杂度是O(N^1.3)
void ShellSort(int* a, int n)
{
	int gap = n;

	while (gap > 1)
	{
		//gap = gap / 2;  // logN
		gap = gap / 3 + 1; // log3N 以3为底数的对数
		// gap > 1时都是预排序  接近有序
		// gap == 1时就是直接插入排序 有序

		// gap很大时,下面预排序时间复杂度O(N)
		// gap很小时,数组已经很接近有序了,这时差不多也是(N)

		// 把间隔为gap的多组数据同时排
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}	
}

void TestShellSort()
{
	int a[] = { 3, 5, 2, 7, 8, 6, 1, 9, 4, 0 };
	ShellSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1; // 默认是左孩子
	while (child < n)
	{
		// 1、选出左右孩子中大的那一个
		if (child + 1 < n && a[child+1] > a[child])
		{
			child += 1;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	// 建堆  O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}

	// 排升序,建大堆还是小堆?建大堆
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		--end;
	}
}

void TestHeapSort()
{
	int a[] = { 3, 5, 2, 7, 8, 6, 1, 9, 4, 0 };
	HeapSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

// 测试排序的性能对比
void TestOP()
{
	srand(time(0));
	const int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int)*N);
	int* a2 = (int*)malloc(sizeof(int)*N);
	int* a3 = (int*)malloc(sizeof(int)*N);
	int* a4 = (int*)malloc(sizeof(int)*N);
	int* a5 = (int*)malloc(sizeof(int)*N);
	int* a6 = (int*)malloc(sizeof(int)*N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];

	}

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	//SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	//HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	//QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	//MergeSort(a6, N);
	int end6 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	/*printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);*/

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
}

int main()
{
	//TestShellSort();
	//TestOP();
	TestHeapSort();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值