各种排序算法:插入、希尔、选择、堆排、冒泡、快排、归并排序

前言:排序是程序员在面试时经常遇到的面试题,排序方法种类繁多,常见的有插入、希尔、选择、堆排、冒泡、快排、归并排序 这7种排序方式

各种排序的时间复杂度:

一、插入排序:

插入排序是一种简单排序,它的思路是:每次从未排好的序列中选出第一个元素插入到已排好的序列中。它的算法步骤可以大致归纳如下:

1. 从未排好的序列中拿出首元素,并把它赋值给temp变量;

2. 从排好的序列中,依次与temp进行比较,如果元素比temp大,则将元素后移(实际上放置temp的元素位置已经空出)

3. 直到找到一个元素比temp小, 将temp放入该位置;

动图演示:

代码:

void Insertion_Sort(int *arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 1; i < sz; i++)
	{
		int tmp = arr[i];
		for (j = i; (j > 0) && (arr[j - 1] > tmp); j--)
		{
			arr[j] = arr[j-1];
		}
		arr[j] = tmp;
	}
	printf("Insertion_Sort:");
	Print(arr, sz);
}

二、希尔排序:

希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。 该方法的基本思想是:

先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高 。

动图演示:

代码:

void Shell_Sort(int *arr, int sz)
{
	int gap = sz;//gap越小越接近有序
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < sz - gap; i++)//每组同时进行
		{
			int end = i;
			int tmp = arr[end + gap];
 			while (end >= 0 && arr[end] > tmp)
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			arr[end + gap] = tmp;
		}
	}
	printf("Shell_Sort:");
	Print(arr, sz);
}

三、选择排序

选择排序是一种简单直观的排序方法,遍历一遍数组,找到最小的那个元素,与数组第一个元素交换,然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

而且这个方法还可以优化,定义一个左指针,定义一个右指针;左指针从前往后遍历,找到最小的元素放到数组最前面,右指针从后往前遍历,找到最大的元素放到最后面;再继续从未排序的元素中寻找最小和最大的元素,分别放到已排序序列后面和前面。(代码写的是优化版本)

动图演示:

代码:

void Swap(int* x1, int* x2)
{
	int tmp = *x1;
	*x1 = *x2;
	*x2 = tmp;
}
void Select_Sort(int *arr, int sz)
{
	int left = 0;
	int right = sz - 1;
	
	while (left < right)
	{
		int min = left;
		int max = right;
		int i = left;
		while (i < right)
		{
			if (arr[i] > arr[max])
				max = i;//找到最大的数
			if (arr[i] < arr[min])
				min = i;//找到最小的数
			i++;
		}
		Swap(&arr[left], &arr[min]);
		if (left == max)//如果最左边的数恰好最大,而最左边的数在上一行代码已经跟下标为min的数交换过
			max = min;
		Swap(&arr[right], &arr[max]);
		left++;
		right--;
	}
	printf("Select_Sort:");
	Print(arr, sz);
}

四、堆排:

堆排需要借助堆来完成排序,这里我的方法是建大堆,每次选出最大的数在堆顶。堆排序的具体实现方法是:

1.把需要排序的序列按照大堆的形式排好(此时的序列还是无序的,只是堆的逻辑上是父节点元素大于子节点);

2.把堆顶的元素与序列的最后一个元素交换(序列尾部交换后就变成了有序序列);

3.把新的无序序列重新调整为大堆(因为交换后堆顶的元素已经改变,此时可能不满足大堆的条件);

4.把堆顶的元素与无序序列的最后一个交换;

5.重复步骤3,4直到交换到最后一个数为止,此时需要排序的序列就变成了升序。

void AdjustDown(int* arr, int n, int root)
{
	assert(arr);
	int parent = root;
	int child = parent * 2 + 1;

	while (child < n)
	{
		if ((child + 1<n) && (arr[child + 1] > arr[child]))
			child++;
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void Heap_Sort(int *arr, int left, int sz)
{
	assert(arr);
	int i = 0;
	int end = sz-1;
	for (i = (sz - 2) / 2; i >= 0; i--)
	{
		AdjustDown(arr, sz, i);
	}
	while (end)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);//交换后,最大的数在最后面,把其次大的数再放到最上面,接下来再交换
		end--;
	}
	printf("Heap_Sort:");
	Print(arr, sz);
}

五、冒泡排序:

冒泡排序是一种简单的排序方法,它重复的访问需要排序的序列,每次比较两个元素,根据需求选择是否交换这两个数,直到需要排序的序列排序完。

主要逻辑:

1.假设有N个元素,游标从第一位元素开始,若左边的元素比右边的元素大,则交换,若左边的元素比右边的元素小,则不交换;

2.游标移向下一位直到最后一位。在游标移动过程中,可以保证,右边的数一定比左边的数大,因为第一轮遍历是要找出最大的数,并且最大的数在最后一位。

3.同理,要找出第二大的数,重复上述过程,直至找出第N大的数,排序结束。因此时间复杂度是N*N,空间复杂度是N 

动图演示:

代码:

void Bubble_Sort(int *arr, int sz)
{
	int i, j;
	for (i = 0; i < sz; i++)
	{
		for (j = i; j < sz; j++)
		{
			if (arr[i] > arr[j])
			{
				Swap(&arr[i], &arr[j]);
			}
		}
	}
	printf("Bubble_Sort:");
	Print(arr, sz);
}

六、快速排序

快速排序有三种算法,分别是左右指针法、挖坑法和前后指针法,还要一种非递归算法所以总共有四种算法

1.左右指针法:

①.首先我们选取一个key值,这个key值一般是序列的首元素或者尾元素;(我选取的是尾元素)

②.再设置两个指针,我们称呼其为左指针和右指针;

③.左指针从序列头部往后移,左指针找到比key大的元素,右指针从序列尾部往前移,找到比key小的元素,交换这两个元素,直到左右指针相遇;

④.交换步骤③中左右指针相遇时的元素和key;

⑤.此时完成了一个循环,

接下来就是函数的递归使用了,递归时,比key小的部分,左指针不变,右指针变成key-1;比key大的部分,左指针变成key+1,右指针不变。

int GetMid(int *arr,int left, int right)//三数取中
{
	assert(arr);
	int mid = left + ((right - left) >> 1);
	if (arr[left] < arr[right])
	{
		if (arr[right] < arr[mid])
			return right;
		else if (arr[left] > arr[mid])
			return left;
		else
			return mid;
	}
	else//right<left
	{
		if (arr[left] < arr[mid])
			return left;
		else if (arr[right] > arr[mid])
			return right;
		else
			return mid;
	}
}
int Quick_Sort(int* arr, int left,int right)//左右指针法
{
	assert(arr);
	int mid = GetMid(arr, left, right);
	Swap(&arr[mid], &arr[right]);
	int key = right;
	while (left < right)
	{
		while ((left < right)&&(arr[left] <= arr[key]))
		{
			++left;
		}
		while ((left < right)&&(arr[right] >= arr[key]))
		{
			--right;
		}
		Swap(&arr[left], &arr[right]);
		//key = right;
	}
	Swap(&arr[right], &arr[key]);
	return right;
}
void QuickSort(int *arr, int left, int right)
{
	assert(arr);
	if (left > right)
		return;
	int div = Quick_Sort3(arr, left, right);
	QuickSort(arr, left, div - 1);
	QuickSort(arr, div+1, right);
	printf("Quick_Sort:");
	Print(arr, right+1);
}

2.挖坑法:

①.首先我们选取一个key值,这个key值一般是序列的首元素或者尾元素(我选取的是尾元素),并且用tmp记录下这个值(挖坑);

②.同样设置两个指针,左指针和右指针,左指针指向序列头部,右指针指向序列尾部;

③.左指针从序列头部往后移,左指针找到比key大的元素,让右指针指向的值等于左指针指向的值(可以理解为坑变成了左指针指向的位置);

④.右指针从序列尾部往前移,右指针找到比key小的元素,让左指针指向的值等于右指针指向的值(可以理解为坑变成了右指针指向的位置);(此时坑的位置就是在不断在左右变换的)

⑤.步骤③和步骤④放在一个while循环中,循环停下的条件是左指针等于右指针

⑥.交换左右指针相遇位置的元素和tmp;

⑦.此时完成了一个循环,递归的方式跟左右指针法的方式一样。

int Quick_Sort2(int *arr, int left, int right)//挖坑排序法
{
	assert(arr);
	int mid = GetMid(arr, left, right);
	Swap(&arr[mid], &arr[right]);
	int keyindex = right;
	int tmp = arr[keyindex];
	while (left < right)
	{
		while ((left < right) && (arr[left] <= tmp))
		{
			left++;
		}
		arr[right] = arr[left];
		while ((left < right) && (arr[right] >= tmp))
		{
			right--;
		}
		arr[left] = arr[right];
	}
	arr[right] = tmp;
	return right;
}

3.前后指针法:

①.首先我们选取一个key值,这个key值一般是序列的首元素或者尾元素(我选取的是尾元素),并且用tmp记录下这个值(挖坑);

②.设置两个指针,一个前指针prev,一个后指针cur;

③.cur指向序列的第一个位置,prev = cur-1;

④.当cur指向的元素小于key,并且++prev!= cur(防止序列第一个元素就需要交换的情况),交换cur指向的值和prev指向的值;

⑤.不管交换了还是没有交换cur每次都需要++,而prev只是交换了之后才++,此时就形成了一个情况:prev和cur指针中间的元素都是大于key的,而prev之前的元素都是小于key的;

⑥.当cur 移到序列尾部时,一个循环结束,接下来的递归和左右指针法一样

int Quick_Sort3(int *arr, int left, int right)//前后指针法
{
	assert(arr);
	int cur = left;
	int prev = cur - 1;
	int key = arr[right];
	int keyindex = right;
	while (cur < right)
	{
		if (arr[cur] < key && (++prev != cur))
			Swap(&arr[cur], &arr[prev]);
		++cur;
	}
	Swap(&arr[++prev], &arr[keyindex]);
	return prev;
}

4.非递归快速排序方法:

七、归并排序:

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

①.把长度为n的输入序列分成两个长度为n/2的子序列;

②.对这两个子序列分别采用归并排序;

③.将两个排序好的子序列合并成一个最终的排序序列。

 动图演示:

代码:

void Merge(int* arr, int left1, int right1, int left2, int right2, int* tmp)
{
	int index, start,n;
	assert(arr);
	index = left1;
	start = left1;
	n = right2 - left1 + 1;
	while (left1 <= right1 && left2 <= right2)
	{
		if (arr[left1] < arr[left2])
		{
			//tmp[index++] = arr[left2++];//降序
			tmp[index++] = arr[left1++];//升序
		}
		else
		{
			//tmp[index++] = arr[left1++];//降序
			tmp[index++] = arr[left2++];//升序
		}
	}
	while (left1 <= right1)
		tmp[index++] = arr[left1++];
	while (left2 <= right2)
		tmp[index++] = arr[left2++];
	memcpy(arr + start, tmp + start, sizeof(int)*n);
}
void _MergeSort(int* arr,int left, int right,int* tmp)
{
	assert(arr);
	if (left >= right)
		return;
	int mid = left + ((right - left) >> 1);
	//[left,mid][mid+1,right]
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);
	Merge(arr, left, mid, mid + 1, right, tmp);

}
void Merge_Sort(int* arr, int sz)
{
	assert(arr);
	int* tmp = (int*)malloc(sizeof(int)*sz);
	_MergeSort(arr,0,sz-1,tmp);
	printf("Merge_Sort:");
	Print(arr, sz);
	free(tmp);
}

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_41209741/article/details/82831327