Data structure - summary of sorting algorithms (insertion sort, exchange sort, selection sort, two-way merge sort, radix sort)

Unless otherwise specified, the default sorting result is ascending. 

1. Insertion sort

1.1 Direct insertion sorting algorithm

For direct insertion sorting, let A[n] be an array with n numbers to be sorted, and now assume that the position to be sorted is i, and the execution process is (the 0th position is not applicable, starting from the 1st):

1. First save the i-th element, put the i-th element in the 0th position, and act as a sentinel, which can reduce the loop condition, and relatively speaking, it will be optimized a little bit;

2. 1—The elements at position i-1 have been sorted, compare the element at position i with the first i-1 elements in turn, and stop until you find an element that is less than or equal to yourself, assuming the position is k.

3. Move all the elements at positions k+1—i-1 back, and place the i-th element in the vacated position k+1.

Specific code:

//直接插入排序 
void InsertSort(int a[],int n)
{
	int j;
	for(int i=2;i<=n;i++)
	{
		if(a[i]<a[i-1])        //当前元素比其前驱小,将a[i]插入有序表中 
		{
			a[0]=a[i];            //保存当前元素,充当哨兵 
			for(j=i-1;a[j]>a[0];j--)        //从后往前寻找待插入位置 
			{
				a[j+1]=a[j];                //向后移动 
			}
			a[j+1]=a[0];               //将第i个元素插入 
		}
	}
}

Space complexity: O(1);

time complexity:

Best case: O(n); //The original table is sorted, and each time an element is inserted, it only needs to be compared once without moving;

Worst case: O(n); //The original table is also ordered, but in reverse order. The total number of comparisons is maximized, and the number of moves is also maximized.

Average time complexity: O(n^2);     

Stability: Each time the element is moved from the back to the front, there will be no change in the relative position of the same element, stable.

Insertion sorting is suitable for both sequential storage and linear tables for linked storage. But chained storage only reduces the number of moves, the number of comparisons is still on the order of the square, and the time complexity is still on the order of the square.

But it is suitable for the elements to be sorted are basically ordered. 

1.2 Binary insertion sort

The half insertion sort is a small improvement to the direct insertion sort algorithm. The improvement is that when inserting the i-th element, the first i-1 elements are sorted, so in the search step, we can Use half-find to find the position to be inserted, and then move.

Specific code:

//折半插入排序
void InsertSort(int a[],int n)
{
	int i,j,low,high,mid;
	for(i=2;i<=n;i++)
	{
		a[0]=a[i];
		low=1,high=i-1;
		while(low<=high)       //折半查找的思想,寻找待插入位置 
		{
			mid=(low+high)/2;
			if(a[mid]<=a[0])
			{
				low=mid+1;
			}
			else
			high=mid-1;
		}
		for(j=i-1;j>=high+1;j--)          //将low —i-1位置的元素后移 
		{
			a[j+1]=a[j];
		}
		a[high+1]=a[0];         //插入到low的位置 
	}
}

Time complexity: Although the number of comparisons is reduced by the half search, the number of moves remains unchanged. Therefore, the time complexity of the half search is still O(n^2);

When processing the half search, we pay special attention to the situation that the element values ​​are equal. When a[mid]=a[i], we continue to look for a suitable position. The advantage of this is that it can ensure the stability of the insertion sort .

2. Hill sort 

Hill sort is an improvement over direct insertion sort. Because the time complexity of direct sorting is relatively small when the array is relatively ordered, the idea of ​​Hill sorting is: first divide the list to be sorted into several forms such as: a[i, i+d, i+2d... …]’s “special” sub-table, that is, to form a sub-table with records separated by a certain increment, and directly insert and sort each sub-table. When the elements in the entire table are in “basic order”, then Perform a direct insertion sort on all records.

In fact, Hill sorting just adds an increment. Each time the elements of the same increment are compared, after each round of sorting, the increment is halved, but the basic idea is to directly insert the sort. Pay attention to the judgment conditions of the movement of the Hill sort.

Specific code:

//希尔排序
void ShellSort(int a[],int n)
{
	int d;         //增量
	for(d=n/2;d>=1;d/=2)     //不断缩短增量排序 
	{
		for(int i=d+1;i<=n;i+=d)      //前面元素是排好序的,从d+1开始 
		{
			if(a[i]<a[i-d])
			{
				a[0]=a[i];              //这里的a[0]不是起到哨兵的作用,只是保存第i个元素 
				for(int j=i-d;j>0&&a[0]<a[j];j-=d)      //注意判定条件,将同一个子表中符合条件的元素后移 
				{
					a[j+d]=a[j];
				}
				a[j+d]=a[0];           //插入元素 
			}
		}
	} 
 } 

Time complexity: worst case (when d is 1: O(n); when n is in a certain range, the time complexity is about: O(n^1.3).

It can be seen from the sorting process that Hill sorting is an unstable sorting algorithm. And because it needs to use the increment d to find elements belonging to the same sublist, it cannot use a linked list, a storage structure that does not have random access characteristics.

 3. Bubble sort

Bubble sorting is an idea based on exchange. Each pass sorts the maximum or minimum value to the front. After each pass, the element at each position is its final position.

 //冒泡排序
void BubbleSort(int a[],int n)
{
 	for(int i=0;i<n-1;i++)      
 	{
 		bool flag=false;          //表示本趟冒泡排序是否发生交换的标志 
		for(int j=0;j<n-1-i;j++)  
		{
			if(a[j]>a[j+1])          //若逆序 
			{
				swap(a[j],a[j+1]);         //则交换 
				flag=true;
			}
		}
		if(flag==false)             //本趟遍历没有发生交换,说明已经有序 
		return ;	
	}
}

4. Quick Sort 

Quick sort is based on the idea of ​​​​divide and conquer: each time a number is selected in the table to be sorted as the benchmark, and the list to be sorted is divided into two parts through a sorting pass. After each sorting, the elements smaller than the benchmark are on the left , elements larger than the benchmark are on the right, and then the left and right sides are recursively sorted until each part has only one element or is empty.

In this code, it is assumed that the first element is used as the basis for division every time.

Specific code:

#include <iostream>
using namespace std;
int s[100];

int Partition(int a[],int low,int high)
{
	int p=a[low];               //选择基准元素 
	while(low<high)
	{
		while(low<high&&a[high]>=p)   //把基准后面比基准大的元素往前移 
		high--;
		a[low]=a[high];
		while(low<high&&a[low]<p)       //把基准前面比基准小的元素往后移 
		low++;
		a[high]=a[low];
	}
	a[low]=p;                  //把基准放在最终位置 
	return low;                //返回基准位置 
} 

void  QuickSort(int a[],int low,int high)
{
	if(low<high)
	{
		int mid=Partition(a,low,high);
		QuickSort(a,low,mid-1);          //对每次划分的前后部分分别递归处理 
		QuickSort(a,mid+1,high);
	}
}
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>s[i];
	QuickSort(s,0,n-1);
	for(int i=0;i<n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

It can be seen that the key to the quick sorting algorithm lies in partitioning, so the performance of the algorithm mainly depends on the quality of the partitioning operation. Quick sort needs to use recursion, so a recursive work stack is needed as an auxiliary space.

The space complexity is the space required by the stack, in the best case: O(logn); in the worst case, the table is basically ordered or basically reversed. The space complexity is O(n); the average space complexity is: O(logn);

Time complexity: best case: O(nlogn); worst case: O(n);

It can be inferred that quick sort is an unstable algorithm.

Although quicksort does not generate ordered subsequences after each sort, the reference will be placed in its final position after each sort.

5. Selection sort 

5.1 Simple Selection Sort 

Algorithm idea: Each sorting is to select the element with the smallest (or largest) keyword among the elements to be sorted and add it to the ordered subsequence.

Specific code:

//简单排序
void SelectSort(int a[],int n)
{
	for(int i=0;i<n-1;i++)     //n个元素需要n-1趟排序 
	{
		int min=i;
		for(int j=i+1;j<n;j++)       //每次都是寻找最小值 
		{
			if(a[j]<a[min])
			min=j;
		}
		if(min!=i)              //若找到最小值,交换 
		swap(a[i],a[min]);
	}
 } 

Space complexity: O(1);

Time complexity: O(n*n);

This algorithm is not stable, but it is applicable to both ordered lists and linked lists.

5.2 Heap sort 

Heap sorting is an algorithm with relatively good performance. It is also based on the idea of ​​selection sorting. Large top heap: In a complete binary tree, the root >= the left and right nodes. Small top piles are just the opposite.

Heap sorting is divided into: building a heap and outputting the top element of the heap, and then continuing to adjust the heap.

The process of building a heap (taking the big top heap as an example): starting from the last non-terminal node in the complete binary tree, that is, the n/2th node, if the node has left and right children, then judge the left and right of the node Whether the child is bigger than the root node, if so, choose the largest child node to exchange with the root node, but the heap of its child nodes may be destroyed after the exchange, so continue to use the method just now to construct the next level of heap, Until the subtree rooted at this node forms a large top heap.

Sorting process (take the big top heap as an example): each time sorting, the top element of the heap is added to the ordered subsequence (exchanged with the last element in the sequence of elements to be sorted), and the sequence of elements to be sorted is adjusted to the big top again heap.

The specific code of the big top heap:

#include <iostream>
using namespace std;
int s[100];
//大根堆排序
void HeadAdjust(int a[],int k,int len)
{
	a[0]=a[k];          //先把第k个元素存着,以期后面找到合适的位置放置
	for(int i=2*k;i<=len;i*=2)          //顺着k较大的子结点向下筛选 
	{
		if(i<len&&a[i]<a[i+1])       //i<len保证有右孩子,在左孩子和右孩子中选一个最大 
		{
			i++;
		} 
		if(a[0]>=a[i])        //根结点本身就是最大 
		break; 
		else
		{
			a[k]=a[i];           //根结点替换为最大的孩子 
			k=i;                  //孩子结点的大顶堆有可能被破坏,所以沿着k的孩子结点往下判断 
		}
	} 
	a[k]=a[0];                  //被筛选结点的值放入最终位置 
}

void BuiltMaxHeap(int a[],int len)       
{
	for(int i=len/2;i>=1;i--)       //从最后一个非终端结点到根结点调整堆 
	{
		HeadAdjust(a,i,len);
	}
 } 

void HeapSort(int a[],int len)
{
	BuiltMaxHeap(a,len);              //先建堆 
	for(int i=len;i>1;i--)       //n个元素需要进行n-1趟排序 
	{
		swap(a[i],a[1]);           //和堆低元素交换,将最大值后移,实现升序 
		HeadAdjust(a,1,i-1);     
	}
 } 
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>s[i];
	HeapSort(s,n);
	for(int i=1;i<=n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

The analysis process of the small top heap is similar to that of the large top heap, so the code is directly given here. It should be noted that the sorting based on the large top heap is in ascending order, and the sorting based on the small top heap is in descending order.

 The specific code of the small top heap:

#include <iostream>
using namespace std;
int s[100];
//小根堆排序
void HeadAdjust(int a[],int k,int len)
{
	a[0]=a[k];          //先把第k个元素存着,以期后面找到合适的位置放置
	for(int i=2*k;i<=len;i*=2)          //顺着k小的子结点向下筛选 
	{
		if(i<len&&a[i]>a[i+1])       //i<len保证有右孩子,在左孩子和右孩子中选一个最小 
		{
			i++;
		} 
		if(a[0]<=a[i])        //根结点本身就是最小 
		break; 
		else
		{
			a[k]=a[i];           //根结点替换为最小的孩子 
			k=i;                  //孩子结点的小顶堆有可能被破坏,所以沿着k的孩子结点往下判断 
		}
	} 
	a[k]=a[0];                  //被筛选结点的值放入最终位置 
}

void BuiltMinHeap(int a[],int len)       
{
	for(int i=len/2;i>=1;i--)       //从最后一个非终端结点到根结点调整堆 
	{
		HeadAdjust(a,i,len);
	}
 } 

void HeapSort(int a[],int len)
{
	BuiltMinHeap(a,len);              //先建堆 
	for(int i=len;i>1;i--)       //n个元素需要进行n-1趟排序 
	{
		swap(a[i],a[1]);           //和堆低元素交换,将最小值后移,实现降序 
		HeadAdjust(a,1,i-1);     
	}
 } 
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>s[i];
	HeapSort(s,n);
	for(int i=1;i<=n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

Space complexity: O(1);

The time complexity is: best, worst and average are O(nlogn);

It can be deduced that the algorithm is unstable. 

6. Merge sort 

 The algorithm idea of ​​merge sorting: treat n records in the table to be sorted as n ordered sub-tables, but the length of each sub-table is 1, and then merge them in pairs to get n/2 and take the entire length up to 1 Or an ordered list of 2, continue to merge two by two, and repeat until it is merged into an ordered list of length n, this is a 2-way merge sort.

Specific code:

#include <bits/stdc++.h>
using namespace std;

#define n 100
int s[n];

int *b=(int *)malloc(sizeof(int)*(n+1));       //辅助数组B
int i,j,k;
void Merge(int a[],int low,int mid,int high)
{
	for(k=low;k<=high;k++)           //先将表中数据复制到辅助数组中 
	{
		b[k]=a[k];
	}
	for(i=low,j=mid+1,k=i;i<=mid&&j<=high;)
	{
		if(b[i]<=b[j])
		{
			a[k++]=b[i++];
		}
		else
		{
			a[k++]=b[j++];
		}
	}
	while(i<=mid)         //其中有一个表中还有数据 
	a[k++]=b[i++];
	while(j<=high)
	a[k++]=b[j++];
} 
void MergeSort(int a[],int low,int high)
{
	if(low<high)
	{
		int mid=(low+high)/2;          //从中间划分两个子序列 
		MergeSort(a,low,mid);          //对左侧子序列进行递归排序 
		MergeSort(a,mid+1,high);      //对左侧子序列进行递归排序 
		Merge(a,low,mid,high);        //归并 
	}
}
int main(int argc, char** argv) {
	int m;
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>s[i];
	}
	MergeSort(s,1,m);
	for(int i=1;i<=m;i++)
	cout<<s[i]<<" ";
	return 0;
}

The core operation of merge sort is to merge two sorted sequences in an array into one.

Space complexity: O(n); although recursion also requires space, the merge tree can be regarded as an inverted binary tree. recursion depth <logn;

Time complexity: O(nlogn).

7. Radix sorting 

Cardinality sorting is not based on the idea of ​​comparison and movement, but sorting is based on the size of each keyword.

The basic idea of ​​radix sorting (taking the descending sequence as an example):

1. Initialization: Set up r empty queues, Qr-1, Qr-2,..., Q0, according to the order of decreasing weight of each key bit (one, ten, hundred), do "allocation" for each key bit of d ” and “Collect”.

2. Allocation: Scan each element sequentially, if the currently processed key bit = x, then insert the element into the end of the Qx queue.

3. Collection: dequeue and link the nodes in each queue of Qr-1, Qr-2, ..., Q0 in sequence.

Space complexity: Since radix sorting is stored in a chain. Auxiliary space is required for r queues. So the time complexity is O(r).

Time complexity: O(d(n+r)); radix sorting requires d allocation and collection, one allocation requires O (n), and one collection requires O (r).

It can be inferred that radix sorting is stable.

Guess you like

Origin blog.csdn.net/m0_51769031/article/details/125459392