欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

排序_内存排序

程序员文章站 2022-06-04 09:30:41
...

目录

 

0.各种排序算法对比

​1.冒泡排序

2.选择排序

3.直接插入排序

4.Shell排序(缩小增量插入排序)

5.归并排序

6.快速排序

7.堆排序和优先队列

7.1堆排序

7.1.1初始建堆

7.1.2 输出堆顶后调整为新堆

7.2利用建立堆实现一优先队列


0.各种排序算法对比

排序_内存排序
1.冒泡排序

//冒泡排序,平均时间O(n^2),最好O(n),最坏O(n^2),空间复杂度O(1),不稳定
void BubbleSort(vector<int>& vec)
{
	int temp;
	int n = vec.size();
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			if (vec[j]>vec[j + 1])
			{
				swap(vec[j], vec[j + 1]);
			}
		}
	}
}

2.选择排序

//选择排序,时间复杂度O(n^2),空间复杂度O(1),不稳定
void SelectSort(vector<int>& vec)
{
	int temp, k;
	int n = vec.size();
	for (int i = 0; i < n - 1; i++)
	{
		k = i;
		for (int j = i + 1; j < n; j++)
		{//找出关键字最小的下标k
			if (vec[j] < vec[k])	k = j;
		}//for
		if (k != i)
		{
			swap(vec[i], vec[k]);
		}//if
	}//for
}//SelectSort

3.直接插入排序


//直接插入排序:最好时间复杂度O(n),平均时间复杂度O(n^2),最坏O(n^2),辅助空间O(1),稳定
//将一个记录插入到以及排好序的有序表中,从而得到一个新的,记录数增1的有序表
void InsertSort(vector<int>& L)
{//先将序列中的第一个记录看成有序子序列,然后从第二个记录起逐个进行插入,直至整个序列变成按关键字有序的非递减有序序列
	if (L.size() < 2)
	{
		return;
	}
	int temp;
	int j = 0;
	for (int i = 1; i < L.size(); i++)
	{
		if (L[i] < L[i - 1])//需要将L[i]插入前面的有序子表L[0...i-1]中,L[0...i-1]已序
		{
			temp = L[i];//temp是哨兵,将L[i]暂时存下来
			for (j = i - 1; j >= 0&&temp < L[j]; j--)
			{
				L[j + 1] = L[j];//记录后移
			}
			L[j + 1] = temp;//哨兵temp插入到正确的位置
		}
	}
}//InsertSort
/*
{ 49, 38, 65, 97, 76, 13, 27, 49 };  0

{38,49,65, 97, 76, 13, 27, 49 };   1  (整个排序过程为n-1趟插入)

{38,49,65, 97, 76, 13, 27, 49 };  2

{38,49,65, 97, 76, 13, 27, 49 };  3

{38,49,65,76,97,13,27,49};   4

{13,38,49,65,76,97,27,49};   5

{13,27,38,49,65,76,97,49};  6

{13,27,38,49,49,65,76,97};  7


*/

4.Shell排序(缩小增量插入排序)


//直接插入排序与希尔排序对比着看
//希尔排序,缩小增量排序,是对直接插入排序的改进,时间复杂度O(n^1.3),空间复杂度O(1),不稳定
void ShellSort(vector<int>& L)
{
	int i, j, k, temp, dk;
	int n = L.size();
	k = L.size();//k=n元素个数
	vector<int> delta;//增量序列数组
	/*
	从k=n开始,重复k=k/2运算,直到k等于1,所得k值的序列作为增量序列存入delta数组
	*/
	do{
		k = k / 2;
		delta.push_back(k);
	} while (k >= 1);//最后一个增量必须是1!!
	i = 0;
	while ((dk = delta[i]) > 0)
	{
		for (k = delta[i]; k < n; k++)
		{
			if (L[k] < L[k - dk])//将L[k]插入到有序增量子表中
			{
				temp = L[k];//备份待插入元素,空出L[k]的位置!
				for (j = k - dk; j >= 0 && temp < L[j]; j -= dk)
				{
					L[j + dk] = L[j];//寻找待插入位置的同时,将记录以dk为增量进行后移
				}
				L[j + dk] = temp;//找到待插入位置,插入temp
			}//if
		}
		//每一次while循环排好一个增量的子序列!!
		++i;	//取下一个增量值
	}//while
}//ShellSort

5.归并排序

void Merge(int SR[], int TR[], int start, int mid, int end)
{//将有序的SR[start,m] 和 SR[m+1,end]合并成有序表TR[start..end]
	int j, k;
	for (j = mid + 1, k = start; start <= mid && j <= end; ++k)
	{//将SR中的记录由小到大合并到TR中
		if (SR[start] <= SR[j])	TR[k] = SR[start++];
		else TR[k] = SR[j++];
	}
	if (start <= mid)
	{//TR[k..end]=SR[start..mid]  将剩余的SR[start..mid]复制到TR
		while (start <= mid)
		{
			TR[k++] = SR[start++];
		}
	}
	if (j <= end)
	{//TR[k..end]=SR[j..end]  将剩余的SR[j..end]复制到TR
		while (j <= end)
		{
			TR[k++] = SR[j++];
		}
	}
}
void MSort(int data1[], int data2[], int start, int end)
{
	int mid;
	int *temp;
	if (start == end)	data2[start] = data1[start];
	else
	{
		temp = new int[end - start + 1];//申请辅助空间
		mid = (start + end) / 2;	//将data1[start...end]均分为data1[start...mid] 和data1[mid+1...end]
		MSort(data1, temp, start, mid);//递归地将data1[stard...mid]归并为有序的temp[start...mid]
		MSort(data1, temp, mid + 1, end);//递归地将data1[mid+1...end]归并为有序的temp[mid+1...end]
		Merge(temp, data2, start, mid, end);//将temp[start...mid]和temp[mod+1...end]归并到data2[start...end]
		//delete temp;	//有个bug :Heap block at 00F8D298 modified at 00F8D2CC past requested size of 2c	
	}
}//MSort


void MergeSort(int data[], int n)
{
	MSort(data, data, 0, n - 1);
}//MergeSort

6.快速排序

 
/*对顺序表L的快速排序:*/
//一次划分,将枢轴记录放到最终有序表的正确的位置上!
int Partition(vector<int> &L, int low, int high)
{
	int pivotkey = L[low];//设立枢轴记录,这里是这区间的第一个记录为枢轴记录。
    /*枢轴记录的选择有三策略:1.选区间第一个元素;2.在区间中随机选一个元素;3.三者取中法(选区间
    头尾和中间的三个元素中的中位数(中值)为枢轴记录,这样有利于应该关键字基本有序或已序的情况*/
	while (low < high)
	{//升序排序
		while (low < high&&L[high] >= pivotkey)--high;
		L[low] = L[high];
		while (low < high&&L[low] <= pivotkey)++low;
		L[high] = L[low];
	}
	L[low] = pivotkey;//枢轴记录归位
	return low;//返回枢轴记录的位置
}
 
void QSort(vector<int>& L, int low, int high)
{//对顺序表L[low,...,high]的子序列做快速排序
	int pivotLoc;
	if (low < high)    //长度大于1
	{
		pivotLoc = Partition(L, low, high);
		QSort(L, low, pivotLoc - 1);
		QSort(L, pivotLoc + 1, high);
	}
}
 
void QuickSort(vector<int>& L)
{//对顺序表L做快速排序!
	QSort(L, 0, L.size() - 1);
}

7.堆排序和优先队列

7.1堆排序

7.1.1初始建堆

初始序列{55,60,40,10,80,65,15,5,75}建立大根堆的过程如下图:

排序_内存排序

 

7.1.2 输出堆顶后调整为新堆

建好大顶堆,输出堆顶元素后,调整为新堆的过程:

排序_内存排序


/*
在对Ki为根的子树建堆的过程中,可能需要交换Ki和K2i(或K2i+1)的值,如此一来,
以K2i(K2i+1)为根的子树可能不再满足堆的定义,则应继续以K2i(或K2i+1)为根进行调整,
如此层层递推下去,可能会一直延伸到树叶为止,
这样就像过筛子一样,把最大的关键字一层一层地筛选出来,最后输出堆顶最大的元素!
*/

void HeapAdjust(HeapType& H, int s, int m)
{//本函数调整H[s]的关键字,使得H[s ... m]成为一个大顶堆(对其中记录的关键字而言)
	int rc = H[s];
	for (int j = 2 * s+1; j <=m; j*=2+1)//沿值较大的孩子结点向下筛选!
	{//注意,H的下标从0计起,所以j是s的左子树根,j+1是s的右子树根
		if (j < m && H[j] < H[j + 1])	++j;//j为关键字较大的记录的下标,即j最终要指向s的值较大的孩子结点
		if (!(rc < H[j]))	break;	//rc应插在位置s处,如果rc>=H[j],即s>=其左右孩子,则不需要作调整,跳出循环
		H[s] = H[j];//如果s<(s的值较大的孩子j),则交换之,
		s = j;//用s记录下待插入元素的下标
	}
	H[s] = rc;	//将备份元素插入由s所指出的插入位置!
	//这样写的巧妙就是将(s>=其左右孩子)和s<(s的值较大的孩子j) 这样两种情况都统一了起来
}//HeapAdjust


void HeapSort(HeapType& H)
{//对顺序表H进行堆排序,最后H升序有序
	for (int i = H.size() / 2 - 1; i >= 0; --i)//把H[i,...H.size()-1]建成大顶堆
	{//从最后一个非终端节点往回逐一调整,这个循环结束将建成一大顶堆!
		HeapAdjust(H, i, H.size()-1);
	}
	for (int i = H.size() - 1; i > 0; i--)
	{//将堆顶记录H[0]和当前未经排序的子序列H[1...i]中的最后一个记录交换
		swap(H[0], H[i]);
		HeapAdjust(H, 0, i - 1);//将H[0,i-1]重新调整为一大顶堆,即使得最大值放置于H[0]处
	}
}//HeapSort

7.2利用建立堆实现一优先队列

typedef deque<int> HeapType;

//建立优先队列,key值大的优先级高:如key 1<2
/*这里存的是int型,按理来说应该存储自定义类的key-value对的,稍微改一下就行,
但基本要会写HeapAdjust()函数,和BuildHeap建堆的函数,以及稍微改一下BuildHeap成HeapSort()函数!
*/
class MyPriorityQueue{
private:
	HeapType heap;
public:
	void enQueue(int element)
	{
		heap.push_back(element);
		//BuildHeap();//这里没必有进行排序,只需要再次建立堆即可
	}

	int deQueue()
	{
		if (heap.empty())
		{
			throw new std::invalid_argument("invalid_argument");
		}
		int ans= heap.front();
		heap.pop_front();//这里为了方便从线性表的头部删除元素,使用了双端队列deque作容器
		BuildHeap();
		return ans;
	}

	void BuildHeap()
	{
		if (heap.empty())
		{//空,不build
			return;
		}
		else if (heap.size() == 1)
		{//只有一个结点,也不build!
			return;
		}
		else
		{
			for (int i = heap.size() / 2 - 1; i >= 0; --i)//把H[i,...H.size()-1]建成大顶堆
			{//从最后一个非终端节点往回逐一调整,这个循环结束将建成一大顶堆!
				HeapAdjust(heap, i, heap.size() - 1);
			}
		}

	}

	void HeapAdjust(HeapType& H, int s, int m)
	{//本函数调整H[s]的关键字,使得H[s ... m]成为一个大顶堆(对其中记录的关键字而言)
		int rc = H[s];
		for (int j = 2 * s + 1; j <= m; j *= 2+1)
		{//注意,H的下标从0计起,所以j是s的左子树根,j+1是s的右子树根
			if (j < m && H[j] < H[j + 1])	++j;//j为关键字较大的记录的下标,即j最终要指向s的值较大的孩子结点
			if (!(rc < H[j]))	break;	//rc应插在位置s处,如果rc>=H[j],即s>=其左右孩子,则不需要作调整,跳出循环
			H[s] = H[j];//如果s<(s的值较大的孩子j),则交换之,
			s = j;
		}
		H[s] = rc;	//这样写的巧妙就是将(s>=其左右孩子)和s<(s的值较大的孩子j) 这样两种情况都统一了起来,放在循环里面分情况写也是一样的!
	}//HeapAdjust

	void printEmelent()
	{
		for (auto ele : heap)
		{
			cout << ele << " ";
		}
		cout << endl;
	}

};

排序  内部排序  数据结构