八大内部排序

概述

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
在这里插入图片描述
我们这里讲的是内部排序。

1、直接插入排序

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

在这里插入图片描述

算法实现

//第一种排序算法:直接插入排序:如果数组基本有序,我们就用直接插入排序,越有序,时间复杂度越小,极端情况下为O(n)

void InsertSort(int arr[], int len)
{
	//用临时量tmp保存关键值,从后向前找,比它小的或者走到了头,就将关键值放到下一个位置上
	assert(arr != NULL);
	if(NULL == arr)
		return;
	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; 
	}
}

算法效率分析:
1、时间复杂度O(n^2)
2、空间复杂度O(1)
3、算法稳定性:稳定的

2、希尔排序

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
在这里插入图片描述
算法实现

static void Shell(int arr[], int n, int dk)//dk 分成多少间隔
{
	int tmp = 0;
	int j=0;
	for(int i=dk; i<n; i++)//i开始的位置
	{
		tmp = arr[i];           //记录为哨兵          
		for(j=i-dk; j>=0; j=j-dk)
		{
			if(arr[j] > tmp)
			{
				arr[j+dk] = arr[j];     //元素后移  
			}
			else
			{
				break;
			}
		}
		arr[j+dk] = tmp;  //插入到正确的位置
	}
}


//按增量dk(n/2),n为要排序数的个数进行希尔排序
void ShellSort(int arr[], int n)
{
	int dk = n / 2;
	while(dk>=1)
	{
		Shell(arr, n, dk[i]);
		dk /= 2;
	}
}

算法效率分析:
1.时间复杂度O(n^1.3~1.5)
2.空间复杂度O(1)
3.稳定性:发生了跳跃交换,所以不稳定

3.冒泡排序

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往后移动,较小的往前移动。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
在这里插入图片描述
算法实现:

//冒泡排序:两两比较, 大的向后挪
void BubbleSort(int arr[], int len)
{
	int tmp = 0;
	for(int i=0; i<len-1; i++)//次数  
	{
		for(int j=0;j+1<len-i; j++)//两两比较,大的向后挪
		{
			if(arr[j] > arr[j+1])
			{
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}

}

算法效率分析:
1、时间复杂度O(n^2)
2、空间复杂度O(1)
3、算法稳定性:稳定的

4、归并排序

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

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

在这里插入图片描述

算法实现:

//一次归并 以gap合并
static void Merge(int arr[], int len, int gap)//gap 标志几几合并
{
	int *brr = (int*)malloc(sizeof(int) * len);
	assert(brr != NULL);
	int low1 = 0;
	int high1 = low1 + gap -1;
	int low2 = high1 + 1;
	int high2 = low2+gap-1<len ? low2+gap-1 : len-1;//H2 有可能越界
	int i = 0;


	while(low2 < len)//有两个有序段 
	{
		while(low1 <= high1 && low2 <= high2)//两个段内头指针都没走到尾巴
		{
			if(arr[low1] <= arr[low2])
			{
				brr[i++] = arr[low1++];
			}
			else
			{
				brr[i++] = arr[low2++];
			}
		}
	   //左边的段走到尾,那直接将右边的段内数据向下拷贝到brr内即可
		while(low2 <= high2)
		{
			brr[i++] = arr[low2++];
		}
	   //右边的段走到尾,那直接将左边的段内数据向下拷贝到brr内即可
		while(low1 <= high1)
		{
			brr[i++] = arr[low1++];
		}
	   
		//更改L1L2 H1H1,让指向接下来的两个有序段即可 
		low1 = high2 + 1;
		high1 = low1+gap-1;
		low2 = high1 + 1;
		high2 = low2+gap-1<len ? low2+gap-1 : len-1;
	}
	//只有一个有序段
	while(low1 < len)
	{
		brr[i++] = arr[low1++]; 
	}

	//将brr里的全部值拷贝到arr里面,然后将brr释放
	for(int j=0; j<len; j++)
	{
		arr[j] = brr[j];
	}
	free(brr);
	brr = NULL;
}

void MergeSort(int arr[], int len)
{
	for(int i=1; i<len; i*=2)
	{
		Merge(arr, len, i);
	}
}

算法分析:
1、时间复杂度:平均O(n log2 n)
2、空间复杂度:O(n)
3、算法稳定性:稳定

5、简单选择排序

在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
在这里插入图片描述
算法实现:

void SelectSort(int arr[], int len)
{
	int minindex;//存放最小值的下标
	for(int i=0; i<len-1; i++)
	{
		minindex = i;//循环开始前先认为待排序队列的第一个值是最小值
		for(int j = i+1; j<len; j++)
		{
			if(arr[minindex] > arr[j])
			{
			minindex = j;
			}
		}
		if(minindex != i)//这个if判断可以省略,不影响
		{
		int tmp = arr[minindex];
		arr[minindex] = arr[i];
		arr[i] = tmp;
		}
	}
}

算法分析:
1、时间复杂度O(n^2)
2、空间复杂度O(1)
3、算法稳定性:不稳定的

6、堆排序

初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
在这里插入图片描述
调整要点:
1.从最后一个非叶子节点子树开始从后向前调整
2.调整的时候顺序是从上向下
3.升序(大顶堆),降序(小顶堆)

算法实现:

// O(logn)
void OneAdjust(int *arr, int len, int root)
{
	int i = root;
	int j = 2 * i + 1; // j是root的左孩子下标
	int tmp = arr[i];

	while (j < len)
	{
		// 找到左右孩子中较大的一个孩子
		if (j + 1 < len && arr[j] < arr[j + 1]) //  判断有没有右孩子,如果右,判断右孩子是否大于左孩子
		{
			j = j + 1;
		} //  if判断执行完成之后,可以知道j是较大的孩子的下标

		if (tmp > arr[j])
		{
			break;
		}

		arr[i] = arr[j];
		i = j;
		j = 2 * i + 1;
	}

	arr[i] = tmp;
}

void CreateHeap(int *arr, int len)
{
	// root为最后一颗子树的根节点
	int root = (len - 2) / 2; 

	for (; root >= 0; root--)
	{
		OneAdjust(arr, len, root);
	}
}

void HeapSort(int *arr, int len)
{
	CreateHeap(arr, len);

	for (int i = 0; i < len - 1; ++i)
	{
		Swap(&arr[0], &arr[len - 1 - i]);
		OneAdjust(arr, len-i-1 , 0);
	}
}

算法分析:
1、时间复杂度:时间复杂度O(logn)
2、空间复杂度:O(1)
3、算法稳定性:不稳定

7、基数(桶)排序

基数(桶)排序, 低位优先,所有数据从低(个)位开始,依次放入到对应的10个桶内(入队),再依次从桶中将数据依次取出(出队),直到所有数据全部有序。 循环的次数和最大值的位数有关。
在这里插入图片描述
在这里插入图片描述

算法实现:

int GetMaxDigits(int *arr, int len)
{
	int maxValue = arr[0];
	for (int i = 0; i < len; ++i)
	{
		if (arr[i] > maxValue)
		{
			maxValue = arr[i];
		}
	}

	// 计算最大的位数  maxValue = 1234
	int digits = 0;
	while (maxValue)
	{
		digits++;
		maxValue /= 10;
	}

	return digits;
}

int GetDigitsValue(int value, int digits) //  value = 1234  digits = 0  返回值为4
{
	while (digits)
	{
		value /= 10;
		digits--;
	}

	return value % 10;
}

// 时间复杂度: O(d*n)   空间复杂度: O(n)  稳定的
void RadixSort(int *arr, int len)
{
	//1、获取所有数据中最大的关键字的个数
	int maxDigits = GetMaxDigits(arr, len);

	//2、申请关键字范围个数的队列  0 -- 9
	SqQueue  que[10];
	for (int i = 0; i < 10; ++i)
	{
		InitQueue(&que[i], len + 1);
	}

	//3、循环根据各关键字将数据依次存储到对应的队列中,最后在将队列依次输出
	for (int i = 0; i < maxDigits; ++i)
	{
		//3.1 将所有的数据依次存储在对应队列中
		for (int j = 0; j < len; ++j)
		{
			int digValue = GetDigitsValue(arr[j], i);
			PushQueue(&que[digValue], arr[j]);
		}

		//3.2 将队列中的所有数据依次输出到arr中
		int index = 0;
		for (int k = 0; k < 10; ++k)
		{
			while (!EmptyQueue(&que[k]))
			{
				PopQueue(&que[k], &arr[index]);
				index++;
			}
		}
	}

	// 释放所有的队列
	for (int i = 0; i < 10; ++i)
	{
		DestroyQueue(&que[i]);
	}
}

算法分析:
1、时间复杂度:O(d*n)
2、空间复杂度:O(n)
3、算法稳定性:稳定

8、快速排序

1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。

3)此时基准元素在其排好序后的正确位置

4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
在这里插入图片描述
递归算法实现:

static int Partition(int arr[],int left, int right)
{
	int tmp = arr[left];
	while(left < right)//进来保证有两个值
	{
		while(left < right && arr[right] > tmp)
		right--;
		if(left == right)
		{
		break;
		}
		arr[left] = arr[right];
		while(left < right && arr[left] <= tmp)
		left++;
		if(left == right)
		{
		break;
		}
		arr[right] = arr[left];
		}
		arr[left] = tmp;//arr[right] = tmp;
		return left;//return right; 因为此时left == right
}
static void Quick(int arr[], int left, int right)
{
	if(left < right)
	{
	int midindex = Partition(arr, left, right);
	Quick(arr, left, midindex-1);
	Quick(arr, midindex+1, right);
}
}

非递归实现:

void QuickNoR(int *arr, int start, int end)
{
	SqStack st;
	InitStack(&st);
	PushStack(&st, start); 
	PushStack(&st, end);

	while (!EmptyStack(&st))
	{
		int right = 0;
		PopStack(&st, &right);
		int left = 0;
		PopStack(&st, &left);
		int mod = OneQuick(arr, left, right);
		if (mod - left > 1)
		{
			PushStack(&st, left);
			PushStack(&st, mod - 1);
		}
		if (right - mod > 1)
		{
			PushStack(&st, mod + 1);
			PushStack(&st, right);
		}
	}
}

void QuickSort(int *arr, int len)
{
	QuickNoR(arr, 0, len - 1);
}

快速排序作为综合效率最好的内部排序算法,有三种优化方法:
第一种优化:如果有效个数特别少,直接调用直接插入排序
第二种优化:三数取中
第三种优化:防止完全有序,自己打乱一下
算法分析:
1、时间复杂度O(nlogn)
2、空间复杂度O(1)
3、算法稳定性:不稳定

排序算法对比:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值