Sort---排序

<span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);">學習了排序算法之後,覺得應該把它總結一遍,順一遍自己的思路。因爲排序算法比較雜,所以下來和大家一起來看看這麼多的排序算法。先來看</span><span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);">比較排序,</span>


我們看下面一張圖


我們就以下面的圖來講一講各種排序,

1.直接插入排序

思想:

每次選擇一個元素K,將K插入到已經有序的部分A[1....i]中,插入之前,要把K和A中的每個元素進行比較,若K<A[n],則將K插入到A[n]之前(n是A中的某個下標),A[n]之後的所有元素都要向後移動,若發現A[i]>K,則K插到A[i]的後面。

時間複雜度:

最好的情況:正向有序,只需要比較n 次,不需要移動,時間複雜度O(N);

最壞的情況:逆序有序,每一個元素都要移動接近n次,時間複雜度O(N²);

平均時間複雜度:O(N²)

穩定性:

穩定性就是兩個相同的元素排序完之後若兩者順序不變,就是穩定性高,反之穩定性低。

K1是有序部分的元素,插入K2,若K2=K1,則把K2插到K1後面即可,K1不需要移動,所以插入排序穩定性較高。

代碼:

<span style="font-family:Times New Roman;font-size:18px;">void InsertSort(int* a, size_t n)
{
	int i = 0;
	for (i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = end + 1;
		while ((end >= 0) && (a[end]>a[tmp]))
		{
			swap(a[end], a[tmp]);
			end--;
			tmp--;		
		}
	}
	
}</span>

2.希爾排序

思想:

希爾排序也是插入排序的一種,實際上是一種分組插入的方法,這樣做的目的是爲了讓大的數據儘快的跑到前面去,先確定一個小於size的分量gap,將所有距離爲gap的元素分爲一組進行插入排序,然後取第二個分量gap(小於原來的gap)繼續上一步操作,直到gap取到1,也就是最後的一次直接插入排序。


時間複雜度:

最壞情況下:小於插入排序的最壞情況,大於插入排序的最好情況,大概是O(N¹·³)

平均情況:O(N¹·³

穩定性:

我們知道一次插入排序是穩定的,但是多次插入排序中,相同的元素可能會在各自的分組中進行移動,就有可能會打亂相同元素原本的順序,所以希爾排序是不穩定的

代碼:

<span style="font-size:18px;">void ShellSort(int* a, size_t n)
{
	int gap = n;
	int i = 0;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//保證最後一次是直接插入排序
		for (i = 0; i< n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while ((a[end] > tmp) && (end >= 0))
			{
				//swap(a[end + gap], a[end]);
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
	}	
}</span>

3.冒泡排序

思想:

通過無序區間中相鄰字段的比較,將一個最大的數冒到區間的尾部,然後縮小空間,再從頭開始比較,每一冒一個當前空間中最大的數,直到區間長度爲1.

時間複雜度:

最好情況:正向有序,只需要比較n次,O(N)

最壞情況:逆向有序,每個數字都要比較n次,並且移動n次,O(N²)

平均情況:O(N²

穩定性:

排序中只會交換兩個相鄰的元素,因此嗎,當兩個相鄰元素相同時,是沒有必要進行交換的,所以冒泡排序是穩定的

代碼:

<span style="font-family:Microsoft YaHei;">void BubbleSort(int* a, size_t n)
{
	int end = n - 1;
	while (end)
	{
		for (int i = 0; i <= end; i++)
		{
			if (a[i - 1] > a[i])
			{
				swap(a[i - 1], a[i]);
			}
		}
		end--;
	}
}
</span>

4.選擇排序

思想:

在未排序區間中找到最大元素,放至區間末尾,再到剩下的元素中找最大元素,放至末尾,直到未排序區間只剩下一個元素

時間複雜度:

最好情況:正向有序,只需要比較,不需要交換,O(N²)

最壞情況:逆向有序,比較並且需要交換嗎,也是O(N²)

平均情況:O(N²)

穩定性:

排序中可能會交換最後一個元素的位置,最後一個元素的位置很不確定,所以不穩定

代碼;

void SelectSort(int* a, size_t n)
{
	int start = 0;
	int end = n - 1;
	while (start < end)
	{
		int MaxIndex = start;
		int MinIndex = start;
		for (int i = start + 1; i <= end; i++)
		{
			if (a[i] >= a[MaxIndex])
			{
				MaxIndex = i;
			}
			if (a[i] <= a[MinIndex])
			{
				MinIndex = i;
			}
		}

		swap(a[start], a[MinIndex]);

		//排除最大的在第一位,最小的在最後一位要交換兩次的情況
		if (a[end] < a[MaxIndex])
		{
			swap(a[end], a[MaxIndex]);
		}

		start++;
		end--;
	}
}


5.快速排序

思想:

  1. 從數列中挑出一個元素作爲基準數。

  2. 分區過程,將比基準數大的放到右邊,小於或等於它的數都放到左邊。

  3. 再對左右區間遞歸執行第二步,直至各區間只有一個數。

時間複雜度:

最好情況下:每次劃分軸值的左側子序列與右側子序列的長度相同,時間複雜度爲O(nlogn),

最壞情況下:待排序序列爲正序或逆序,時間複雜度爲O(n^2);

平均情況下:時間複雜度爲O(nlogn)

代碼;

int GetKey(int* a, int left, int right)
{
	int key = a[right];
	int start = left;
	int end = right;

	while (start < end)
	{
		while ((a[start]<key) && (start<end))
		{
			start++;
		}
		if (a[start]>key)
		{
			a[end] = a[start];
			end--;
		}

		while ((a[end]>key) && (start<end))
		{
			end--;
		}
		if (a[end] < key)
		{
			a[start] = a[end];
			start++;
		}

	}


	a[start] = key;

	return start;
}


// 非遞歸
void QuickSort(int* a, int left, int right)
{
	stack<int> s;
	s.push(right);
	s.push(left);

	while (!s.empty())
	{
		int start = s.top();
		s.pop();
		int end = s.top();
		s.pop();

		if (start < end)
		{
			int div = GetKey(a, start, end);

			s.push(end);
			s.push(div + 1);

			s.push(div - 1);
			s.push(start);
         }

	}
	
}
遞歸:

left,right]
void QuickSort(int* a,int left, int right)
{
	
	if (left < right)
	{
		int pos = GetKey(a, left, right);
        
		QuickSort(a, left, pos-1);
		QuickSort(a, pos + 1, right);
	}	
}

6.歸併排序

思想;將若干個有序序列進行兩兩歸併,直至所有待排記錄都在一個有序序列爲止。

先考慮合併兩個有序數組,基本思路是比較兩個數組的最前面的數,誰小就先取誰,取了後相應的指針就往後移一位。

然後再比較,直至一個數組爲空,最後把另一個數組的剩餘部分複製過來即可。

再考慮遞歸分解,基本思路是將數組分解成 left right,如果這兩個數組內部數據是有序的,

那麼就可以用上面合併數組的 方法將這兩個數組合並排序。如何讓這兩個數組內部是有序的?可以再二分,直至分解出的小組只含有一個元素時爲止,此時認爲該小組內部已有序。然後合併排序相鄰二個小組即可。

時間複雜度:

最好,最壞,平均都是O(nlogn)。

代碼:

oid Merge(int* a, int* tmp, int start, int mid, int end)
{
	int i = start;
	int j = mid + 1;
	int k = start;

	while ((i < mid + 1) && (j < end + 1))
	{
		while ((a[i] <= a[j]) && (i < mid + 1))
		{
			tmp[k++] = a[i++];
		}
		while ((a[i] > a[j]) && (j < end + 1))
		{
			tmp[k++] = a[j++];
		}
	}

	while (i < mid + 1)
	{
		tmp[k++] = a[i++];
	}
	while (j < end + 1)
	{
		tmp[k++] = a[j++];
	}

	for ( i = start; i < k; i++)
	{
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int* tmp, int start, int end)
{
	if (start < end)
	{
		int mid = (start + end) / 2;
		MergeSort(a, tmp, start, mid);
		MergeSort(a, tmp, mid + 1, end);
		Merge(a, tmp, start, mid, end);
	}
	
}

7,堆排

思想:

1,堆排序是對簡單選擇排序的改進。

2,首先將待排序的記錄序列構造成一個堆,此時,選出了堆中所有記錄的最大者即堆頂記錄,然後將他從堆中移走,並將剩下的記錄再調整成堆,這樣又找出了次大的記錄,以此類推,直到堆中只有一個記錄爲止。

時間複雜度;

最好,最壞,平均的時間複雜度都是O(nlogn)

代碼:

void _AdjustDown(int* a, size_t n, int i)
{
	int parent = i;
	int child = 2 * i + 1;
	while (child < n)
	{
		if((child + 1 < n) && (a[child] < a[child + 1]))
		{
			child++;
			//swap(a[child], a[child + 1]);
		}
		if (a[child] > a[parent])
		{
			swap(a[child], a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
	
}


void HeapSort(int* a, size_t n)
{
	int i = 0;
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		_AdjustDown(a, n, i);
	}


	int size = n;
	for (i = 0; i < n; i++)
	{
		swap(a[0], a[n - 1 - i]);
		_AdjustDown(a, --size, 0);
	}


}
就講到這了,小夥伴們聽懂了嗎微笑







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章