排序算法(C++)

一、冒泡排序

从第一个元素开始,依次向后扫描,并对相邻的两个元素进行比较,如果前面的元素大,就将两个元素换位,这样每进行一次循环就会使得最大的元素被排到最后,然后对前n-1个元素也作此循环,然后n-2,n-3……

void Bubble_sort(int *arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int flag = 0;//如果一次循环结束flag仍等于0,说明已经排好序了,可以直接退出
		for (int j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

二、插入排序

依次从序列中取出一个元素i,将i与它之前的数据j依次作比较,如果i小,就继续向前比较,同时将j向后移动一个位置,以便空出可供i插入的位置,直到i更大,就找到插入位置了

void Insert_sort(int *arr, int n)
{
	for (int i = 1; i < n; i++)
	{
		int temp = arr[i];
		int j = i;
		for (; j > 0 && arr[j - 1] > temp; j--)
		{
			arr[j] = arr[j - 1];
		}	
		arr[j] = temp;

	}
}

三、希尔排序

        希尔排序是对插入排序的一种扩展,插入排序是对序列内元素依次扫描的,而希尔排序加入了一个参数D,使得依次扫描变为每隔D个元素扫描一次,进行完一次循环后,减小D,让扫描变得细致一些,最后再依次扫描

void Shell_sort(int *arr, int n)
{	
	for (int D = n / 2; D > 0; D /= 2)
	{
		for (int i = D; i < n; i += D)
		{
			int temp = arr[i];
			int j = i;
			for (; j > 0 && arr[j - D] > temp; j -= D)
			{
				arr[j] = arr[j - D];
			}
			arr[j] = temp;
		}
	}
}

四、堆排序

        堆排序是利用了最大堆的特性,每次只能删除最大元素,而在排序算法中要将删除操作修改一下,把删除的元素放在序列最后,然后让算法访问不到那个元素,再进行删除操作,又把最大的放到了最后,也就是序列的倒数第二个位置,然后再让算法访问不到最后两个元素,以此类推就可以把序列按从小到大顺序排好。

void maxStack(int *arr, int n)//把序列变为最大堆
{
	int pos = n / 2;

	while (pos > 0)
	{
		int m = pos;
		while (m * 2 <= n)
		{
			int _m;
			if (m * 2 + 1 > n)
				_m = m * 2;
			else
				_m = arr[m * 2 - 1] > arr[m * 2] ? (m * 2) : (m * 2 + 1);

			if (arr[m - 1] < arr[_m - 1])
			{
				int temp = arr[m - 1];
				arr[m - 1] = arr[_m - 1];
				arr[_m - 1] = temp;
			}
			m = _m;
		}
		pos--;
	}
}

void sortStack(int *arr, int n)//把最大堆按照上述算法进行排序
{
	int temp = arr[0];
	arr[0] = arr[n - 1];
	arr[n - 1] = temp;
	n--;
	int m = 1;
	while (m * 2 <= n)
	{
		int _m;
		if (m * 2 + 1 > n)
			_m = m * 2;
		else
			_m = arr[m * 2 - 1] > arr[m * 2] ? m * 2 : m * 2 + 1;

		if (arr[m - 1] < arr[_m - 1])
		{
			temp = arr[m - 1];
			arr[m - 1] = arr[_m - 1];
			arr[_m - 1] = temp;
		}
		m = _m;
	}
}

void Stack_sort(int *arr, int n)//提供固定接口
{
	maxStack(arr, n);

	for (int i = n; i > 0; i--)
	{
		sortStack(arr, i);
	}
}

五、归并排序

        将两段顺序序列按照算法合并成一个,就可以形成一个新的顺序序列,当然子序列中可以是一个元素,这样就形成了一种递归的思想

 

void merge(int *arr, int *temp, int L, int R, int rightEnd)
{//L是左半边起点,R是右半边起点
	int leftEnd = R - 1;
	int pos = L;//temp起点

	while (L <= leftEnd && R <= rightEnd)
	{
		if (arr[L] <= arr[R])
			temp[pos++] = arr[L++];
		else
			temp[pos++] = arr[R++];
	}

	while (L <= leftEnd)
		temp[pos++] = arr[L++];
	while (R <= rightEnd)
		temp[pos++] = arr[R++];

	for (int i = 0; i < pos; i++,rightEnd--)
		arr[rightEnd] = temp[rightEnd];
}

然后递归的进行合并

void mSort(int *arr, int *temp, int L,int rightEnd)
{
	int center;//分割点
	if (L < rightEnd)
	{
		center = (L + rightEnd) / 2;
		mSort(arr, temp, L, center);
		mSort(arr, temp, center + 1, rightEnd);
		merge(arr, temp, L, center + 1, rightEnd);
	}
}

最后统一接口

void recursion_Merge_sort(int *arr, int n)
{
	int *temp = new int[n];
	mSort(arr, temp, 0, n - 1);
	delete temp;
}

还可以使用非递归的方法

方法是将原始序列分割为若干个长度为length的段落,将它们两两合并,并导入temp中,注意序列长度不一定能整除length,所以对于最后一个段落要另外处理,如果最后一个段落比length大,将它分成两个段落进行合并,如果比length小,直接导入temp中。

void merge_pass(int *arr,int *temp,int n,int length)
{//n是整个序列的长度,length是选择的段落长度
	int i = 0;
	for (; i <= n - 2 * length; i += 2 * length)
	{//由于需要两两合并,因此如果i+2*length超过了n的长度,那么最后一对段落是不完全的,需要额外处理
		Merge(arr, temp, i, i + length, i + 2 * length - 1);
	}
	if (i + length < n)
		Merge(arr, temp, i, i + length, n - 1);
	else
		for (int j = i; j < n; j++)
			temp[j] = arr[j];
}

 与之前的合并算法一样,但是不用将temp再导入arr中了,因为在后续算法中我们需要让arr和temp互为temp,也就是它们之间要导来导去,这样可以节省空间,只要保证结果能导入arr中就可以了

void Merge(int *arr, int *temp, int L, int R, int rightEnd)
{//L是左半边起点,R是右半边起点
	int leftEnd = R - 1;
	int pos = L;//temp起点

	while (L <= leftEnd && R <= rightEnd)
	{
		if (arr[L] <= arr[R])
			temp[pos++] = arr[L++];
		else
			temp[pos++] = arr[R++];
	}

	while (L <= leftEnd)
		temp[pos++] = arr[L++];
	while (R <= rightEnd)
		temp[pos++] = arr[R++];
}
void unrecursion_Merge_sort(int *arr, int n)//统一接口
{
	int length = 1;
	int *temp = new int[n];
	while (length < n)
	{
		merge_pass(arr, temp, n, length);
		length *= 2;
		merge_pass(temp, arr, n, length);
		length *= 2;
	}
	delete temp;
}

六、快速排序

        对于一个序列,我们可以选择其中一个元素pivot,然后让比它小的元素都放在它左边,比它大的都放在它右边,这样我们就可以确定pivot的位置了,然后再递归的对它左边和右边的序列做同样的操作,这样就可以将序列排好

        那么如何找pivot呢,如果pivot每次都在序列的两边,那么时间复杂度将会是O(N^2)数量级的,因此我们尽量每次能够选取中间位置,让时间复杂度趋近于O(NlogN)数量级,这里从三个数中选一个中位数,也可以从更多数中选中位数。

int median3(int *arr, int left, int right)
{//从最左边,最右边,中间选中位数
	int center = (left + right) / 2;
	int temp = 0;
	if (arr[left] > arr[center])
	{
		temp = arr[left];
		arr[left] = arr[center];
		arr[center] = temp;
	}
	if (arr[left] > arr[right])
	{
		temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
	}
	if (arr[center] > arr[right])
	{
		temp = arr[center];
		arr[center] = arr[right];
		arr[right] = temp;
	}

    //把中位数换到right-1位置上,为了方便后续函数处理
	temp = arr[center];
	arr[center] = arr[right - 1];
	arr[right - 1] = temp;
	return arr[right - 1];
}

因为arr[left]已经确定比 pivot小,arr[right]已经确定比pivot大,因此在left+1和right-2范围内进行分割处理,创建指针i和j,使它们分别指向left+1和right-2,如果i指向的数据比pivot小,说明这个数据无需处理,让i指向下一个位置,如果数据比pivot大,那么让i在此处暂停,让j做与i相反的操作,当i和j都暂停时让arr[i]和arr[j]换位,然后再继续扫描,直到i走到j的右边去了,就让arr[i]与arr[light-1]换位,这样就可以实现算法

因为快速排序在数据量小的时候速度不如插入排序,因此引入一个参数CUTOFF,如果要处理的数据量大于CUTOFF,就调用快速排序,否则调用插入排序

void quickSort(int *arr, int left, int right)
{
	if (right - left >= CUTOFF)
	{
		int pivot = median3(arr, left, right);
		int i = left, j = right - 1;
		while (1)
		{
			while (arr[++i] < pivot) {}
			while (arr[--j] > pivot) {}
			if (i < j)
			{
				int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
			}
			else
				break;
		}
		int temp = arr[i];
		arr[i] = arr[right - 1];
		arr[right - 1] = temp;
		quickSort(arr, left, i - 1);
		quickSort(arr, i + 1, right);
	}
	else
	{
		Insert_sort(arr + left, right - left + 1);
	}
}

void Quick_sort(int *arr, int n)//统一接口
{
	quickSort(arr, 0, n - 1);
}

七、表排序

        实质上是把指向序列的指针数组进行排序,这种排序一般用于排序大型结构体序列,也就是搬运整个结构体时时间复杂度和空间复杂度过高,就不如将指针进行排序

 第一行是arr的下标,第二行是用来比较大小的成员变量,第三行是指针数组,里面存放着arr每个元素的地址,这里用arr的下标代指

class test
{
public:
	int key;//用来比较大小的成员变量
	test(int k):key(k){};
};

void Table_sort(test *arr,test **table, int n)//arr是数组,table是指针数组,n是数据个数
{
	for (int i = 0; i < n; i++)
	{
		table[i] = &arr[i];//把数组中每个元素的地址存放到指针数组中
	}

	for (int i = 1; i < n; i++)//对指针数组做插入排序
	{
		test *temp = &arr[i];
		int j = i;
		for (; j > 0 && table[j - 1]->key > temp->key; j--)
		{
			table[j] = table[j - 1];
		}
		table[j] = temp;
	}
}

如果需要切实的改变序列的顺序,就要用到物理排序,物理排序也是在表排序后再对原数组进行排序,我们发现,arr的下标和table指向的下标其实是错位的,它们可以形成一个个环

/*
原始序列
arr   0 1 2 3 4 5 6 7 8 9
table 5 9 2 0 7 3 1 6 4 8

拆成一个个环
0 5 3    1 9 8 4 7 6    2
5 3 0    9 8 4 7 6 1    2
*/

而且这些环是互不相交的,那么我们就能对依次对环进行处理,依次根据下标i进行扫描,如果&arr[i]与table[i]不一样,那我们就进入了一个环,先把arr[i]复制一份存放起来,然后把table[i]指向的arr赋给arr[i],然后我们访问table[i]指向的位置,为了确定何时停止循环,我们在处理完arr[i]后,需要把table[i]的指向变成当前的arr[i]

例如,上述的第一个环,碰到arr[0]时发现table[0]指向arr[5],这时进入循环,先把arr[0]复制一份存放起来,然后把arr[5]放到arr[0]的位置上,把table[0]指向arr[0],这一列处理完了,然后我们隔一列判断&arr[3]是否和table[3]相等,不相等,说明环还未走完,那就处理arr[5]和table[5],然后再隔一列判断&arr[0]和table[0]是否相等,因为我们之前已经处理过arr[0]和table[0]了,table[0]指向的地址正是arr[0],说明环被扫描完了,这时我们将之前复制的arr[0]放到arr[3]中就可以了

void real_Table_sort(test *arr, test **table, int n)
{
	Table_sort(arr, table, n);

	for (int i = 0; i < n; i++)
	{
		if (table[i] != &arr[i])
		{
			int pos = i;
			int before = pos;
			test temp = arr[i];
			while (table[table[pos] - &arr[0]] != &arr[table[pos] - &arr[0]])
			{				
				pos = table[pos] - &arr[0];
				arr[before] = arr[pos];
				table[before] = &arr[before];
				before = pos;
			}
			arr[pos] = temp;
			table[pos] = &arr[pos];
		}
	}
}

八、基数排序

        基数排序是根据不同维度依次进行排序的方法,有主位优先MSD和次位优先LSD两种,例如我们要排若干个三位数,我们是先根据百位数再根据十位数最后根据个位数排序的,那么百位数对于十位数来说就是主位,十位数就是次位

        在基数排序中,我们需要额外的空间作为“桶”来存放按照不同基数进行排序后的结果

class bucket//需要创建链表作为桶
{
public:
	int num;
	bucket*next;
	bucket() :num(0), next(NULL) {};
	bucket(int val) :num(val), next(NULL) {};
};

void LSD(int*arr,int n)
{
	bucket buc[10];//将不同类的桶放入数组中
	for (int i = 0; i < 10; i++)
	{
		buc[i] = i;//为每个表头赋予一个类别
	}
	int digit = (int)(log(n - 1) / log(10)) + 1;//获取最高位的位数,注意我这里的n是一个范围,arr在0-n范围内随机选取数字,如果不是,请找到最大值并获取它的位数

	bucket*list = new bucket;//将arr的数据放在链表里
	bucket*pos = list;
	for (int i = 0; i < n; i++)
	{
		bucket*node = new bucket(arr[i]);
		pos->next = node;
		pos = pos->next;
	}

	for (int num = 0; num < digit; num++)//依次对每个基数进行排序
	{
		for (pos = list->next; pos != NULL; )
		{
			int m = pos->num / (int)pow(10, num) % 10;//根据循环次数,分别获取个位数、十位数、百位数数字
			bucket*temp = pos;//用temp暂时指向需要排序的结点
			pos = pos->next;
			bucket*state = &buc[m];//用state访问桶的最后一个结点
			while (state->next != NULL)
			{
				state = state->next;
			}
			state->next = temp;//把需要排序的结点插入到桶的最后
			temp->next = NULL;
		}
		pos = list;
		for (int i = 0; i < 10; i++)//按照桶的顺序把桶中的数据返回到链表中
		{
			bucket*temp = &buc[i];
			while (temp->next != NULL)
			{
				temp = temp->next;
				pos->next = temp;
				pos = pos->next;
			}
		}
		for (int i = 0; i < 10; i++)//让桶恢复空链表状态,准备下一次循环
		{
			buc[i].next = NULL;
		}
	}

	int index = 0;
	for (pos = list->next; pos != NULL; )//循环结束,将链表中数据依次导入数组中,不要忘了释放申请的空间
	{
		arr[index++] = pos->num;
		bucket*release = pos;
		pos = pos->next;
		delete release;
		release = NULL;
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值