数据结构笔记--排序

经典排序算法

算法分类

十种常见排序算法可以分为两大类:
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
在这里插入图片描述

算法复杂度

在这里插入图片描述
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

冒泡排序(Bubble Sort)
  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对(N-1)个元素重复以上的步骤(除了最后一个) 重复步骤1~3,直到排序完成。
    在这里插入图片描述
    #include <stdio.h>
    #include <stdlib.h>
    void ShowArray(int*pArr, int len);
    void BabbleSort(int *pArr, int len);
    
    int main()
    {
    	int array[10] = {-1, 9, 5, 0, -10, 44, 77, 33, -2, -99};
    	int length = sizeof(array) / sizeof(array[0]);
    	ShowArray(array, length);
    	BabbleSort(array, length);
    	printf("排序后的数组为:");
    	ShowArray(array, length);
    	system("PAUSE");
    	return 0;
    }
    //默认为增序排序
    void BabbleSort(int *pArr, int len)
    {	
    	for (int i = 0; i < len - 1; i++)
    	{
    		for (int j = i+1; j < len; j++)
    		{
    			if (pArr[i] > pArr[j])
    			{
    				int tmp = pArr[i];
    				pArr[i] = pArr[j];
    				pArr[j] = tmp;
    			}
    		}
    	}
    }  
    void ShowArray(int *pArr, int len)
    {
    	for (int i=0; i<len; i++)
    	{
    		printf("%d ", pArr[i]);
    	}
    	printf("\n");
    }
选择排序(Selection Sort)
  • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  • 以此类推,直到所有元素均排序完毕。

在这里插入图片描述

    #include <stdio.h>
    #include <stdlib.h>
    void ShowArray(int*pArr, int len);
    void SelectionSort(int *pArr, int len);
    
    int main()
    {
    	int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
    	int length = sizeof(array) / sizeof(array[0]);
    	ShowArray(array, length);
    	SelectionSort(array, length);
    	printf("排序后的数组为:");
    	ShowArray(array, length);
    	system("PAUSE");
    	return 0;
    }
    //默认为增序排序
    void SelectionSort(int *pArr, int len)
    {
    	int minIndex;
    	for (int i = 0; i < len - 1; i++)
    	{
    		minIndex = i;
    		for (int j = i + 1; j < len; j++)
    		{
    			if (pArr[minIndex] > pArr[j])
    			{
    				minIndex = j; // search min value
    			}
    		}
    		int tmp = pArr[i];
    		pArr[i] = pArr[minIndex];
    		pArr[minIndex] = tmp;
    	}
    }
    
    void ShowArray(int *pArr, int len)
    {
    	for (int i = 0; i < len; i++)
    	{
    		printf("%d ", pArr[i]);
    	}
    	printf("\n");
    }

插入排序(Insertion Sort)

工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void InsertSort(int *pArr, int len);

int main()
{
	int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int length = sizeof(array) / sizeof(array[0]);
	ShowArray(array, length);
	InsertSort(array, length);
	printf("排序后的数组为:");
	ShowArray(array, length);
	system("PAUSE");
	return 0;
}
//默认为增序排序
void InsertSort(int *pArr, int len)
{
	int preIndex, curr;
	for (int i = 1; i < len; i++)
	{
		curr = pArr[i];
		preIndex = i - 1;
		while (preIndex >= 0 && pArr[preIndex] > curr)
		{
			pArr[preIndex + 1] = pArr[preIndex];
			preIndex--;
		}
		pArr[preIndex + 1] = curr;
	}
}

void ShowArray(int *pArr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", pArr[i]);
	}
	printf("\n");
}
归并排序(Merge Sort)

归并排序包括"从上往下"和"从下往上"2种方式

  1. 从下往上的归并排序:
    将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;
    得到若干个长度为2的有序数列,再将这些数列两两合并;
    得到若干个长度为4的有序数列,再将它们两两合并;
    直接合并成一个数列为止。
  2. 从上往下的归并排序:它与"从下往上"在排序上是反方向的。它基本包括3步:
    ① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
    ② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
    ③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

// 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 将一个数组中的两个相邻有序区间合并成一个
 *
 * 参数说明:
 *     a -- 包含两个有序区间的数组
 *     start -- 第1个有序区间的起始地址。
 *     mid   -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。
 *     end   -- 第2个有序区间的结束地址。
 */
void merge(int a[], int start, int mid, int end)
{
    int *tmp = (int *)malloc((end-start+1)*sizeof(int));    // tmp是汇总2个有序区的临时区域
    int i = start;            // 第1个有序区的索引
    int j = mid + 1;        // 第2个有序区的索引
    int k = 0;                // 临时区域的索引

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

    while(i <= mid)
        tmp[k++] = a[i++];

    while(j <= end)
        tmp[k++] = a[j++];

    // 将排序后的元素,全部都整合到数组a中。
    for (i = 0; i < k; i++)
        a[start + i] = tmp[i];

    free(tmp);
}

/*
 * 归并排序(从上往下)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 数组的起始地址
 *     endi -- 数组的结束地址
 */
void merge_sort_up2down(int a[], int start, int end)
{
    if(a==NULL || start >= end)
        return ;

    int mid = (end + start)/2;
    merge_sort_up2down(a, start, mid); // 递归排序a[start...mid]
    merge_sort_up2down(a, mid+1, end); // 递归排序a[mid+1...end]

    // a[start...mid] 和 a[mid...end]是两个有序空间,
    // 将它们排序成一个有序空间a[start...end]
    merge(a, start, mid, end);
}


/*
 * 对数组a做若干次合并:数组a的总长度为len,将它分为若干个长度为gap的子数组;
 *             将"每2个相邻的子数组" 进行合并排序。
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     len -- 数组的长度
 *     gap -- 子数组的长度
 */
void merge_groups(int a[], int len, int gap)
{
    int i;
    int twolen = 2 * gap;    // 两个相邻的子数组的长度

    // 将"每2个相邻的子数组" 进行合并排序。
    for(i = 0; i+2*gap-1 < len; i+=(2*gap))
    {
        merge(a, i, i+gap-1, i+2*gap-1);
    }

    // 若 i+gap-1 < len-1,则剩余一个子数组没有配对。
    // 将该子数组合并到已排序的数组中。
    if ( i+gap-1 < len-1)
    {
        merge(a, i, i + gap - 1, len - 1);
    }
}

/*
 * 归并排序(从下往上)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     len -- 数组的长度
 */
void merge_sort_down2up(int a[], int len)
{
    int n;

    if (a==NULL || len<=0)
        return ;

    for(n = 1; n < len; n*=2)
        merge_groups(a, len, n);
}

void main()
{
    int i;
    int a[] =  { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
    int ilen = LENGTH(a);

    printf("before sort:");
    for (i=0; i<ilen; i++)
        printf("%d ", a[i]);
    printf("\n");

    merge_sort_up2down(a, 0, ilen-1);        // 归并排序(从上往下)
    //merge_sort_down2up(a, ilen);            // 归并排序(从下往上)

    printf("after  sort:");
    for (i=0; i<ilen; i++)
        printf("%d ", a[i]);
    printf("\n");
}

快速排序(Quick Sort)
  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
 // 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 快速排序
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
 *     r -- 数组的右边界(例如,排序截至到数组末尾,则r=a.length-1)
 */
void quick_sort(int a[], int l, int r)
{
	if (l < r)
	{
		int i, j, x;

		i = l;
		j = r;
		x = a[i];
		while (i < j)
		{
			while (i < j && a[j] > x)
				j--; // 从右向左找第一个小于x的数
			if (i < j)
				a[i++] = a[j];
			while (i < j && a[i] < x)
				i++; // 从左向右找第一个大于x的数
			if (i < j)
				a[j--] = a[i];
		}
		a[i] = x;
		quick_sort(a, l, i - 1); /* 递归调用 */
		quick_sort(a, i + 1, r); /* 递归调用 */
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	quick_sort(a, 0, ilen - 1);

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}
堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
最大堆进行升序排序的基本思想:

  • ① 初始化堆:将数列a[1…n]构造成最大堆。
  • ② 交换数据:将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。
    在这里插入图片描述

(最大堆的性质)在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2i+1);
性质二:索引为i的右孩子的索引是(2
i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
 // 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
#define swap(a,b) (a^=b,b^=a,a^=b)

/*
 * (最大)堆的向下调整算法
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *     其中,N为数组下标索引值,如数组中第1个数对应的N为0。
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
 *     end   -- 截至范围(一般为数组中最后一个元素的索引)
 */
void maxheap_down(int a[], int start, int end)
{
	int c = start;            // 当前(current)节点的位置
	int l = 2 * c + 1;        // 左(left)孩子的位置
	int tmp = a[c];            // 当前(current)节点的大小
	for (; l <= end; c = l, l = 2 * l + 1)
	{
		// "l"是左孩子,"l+1"是右孩子
		if (l < end && a[l] < a[l + 1])
			l++;        // 左右两孩子中选择较大者,即m_heap[l+1]
		if (tmp >= a[l])
			break;        // 调整结束
		else            // 交换值
		{
			a[c] = a[l];
			a[l] = tmp;
		}
	}
}

/*
 * 堆排序(从小到大)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组的长度
 */
void heap_sort_asc(int a[], int n)
{
	int i;

	// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。
	for (i = n / 2 - 1; i >= 0; i--)
		maxheap_down(a, i, n - 1);

	// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
	for (i = n - 1; i > 0; i--)
	{
		// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。
		swap(a[0], a[i]);
		// 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。
		// 即,保证a[i-1]是a[0...i-1]中的最大值。
		maxheap_down(a, 0, i - 1);
	}
}

/*
 * (最小)堆的向下调整算法
 *
 * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *     其中,N为数组下标索引值,如数组中第1个数对应的N为0。
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
 *     end   -- 截至范围(一般为数组中最后一个元素的索引)
 */
void minheap_down(int a[], int start, int end)
{
	int c = start;            // 当前(current)节点的位置
	int l = 2 * c + 1;        // 左(left)孩子的位置
	int tmp = a[c];            // 当前(current)节点的大小
	for (; l <= end; c = l, l = 2 * l + 1)
	{
		// "l"是左孩子,"l+1"是右孩子
		if (l < end && a[l] > a[l + 1])
			l++;        // 左右两孩子中选择较小者
		if (tmp <= a[l])
			break;        // 调整结束
		else            // 交换值
		{
			a[c] = a[l];
			a[l] = tmp;
		}
	}
}

/*
 * 堆排序(从大到小)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组的长度
 */
void heap_sort_desc(int a[], int n)
{
	int i;

	// 从(n/2-1) --> 0逐次遍历每。遍历之后,得到的数组实际上是一个最小堆。
	for (i = n / 2 - 1; i >= 0; i--)
		minheap_down(a, i, n - 1);

	// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
	for (i = n - 1; i > 0; i--)
	{
		// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最小的。
		swap(a[0], a[i]);
		// 调整a[0...i-1],使得a[0...i-1]仍然是一个最小堆。
		// 即,保证a[i-1]是a[0...i-1]中的最小值。
		minheap_down(a, 0, i - 1);
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	heap_sort_asc(a, ilen);            // 升序排列
	//heap_sort_desc(a, ilen);        // 降序排列

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}
希尔排序(Shell Sort)
  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 表中每个元素每次向前移动ti个元素距离的倍数进行比较,若大于(小于)则交换位置,直到本次移动的元素出界
  • 重复第三步,直到ti取1,结束交换

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
 // 数组长度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )

/*
 * 希尔排序
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组的长度
 */
void shell_sort1(int a[], int n)
{
	int i, j, gap;

	// gap为步长,每次减为原来的一半。
	for (gap = n / 2; gap > 0; gap /= 2)
	{
		// 共gap个组,对每一组都执行直接插入排序
		for (i = 0; i < gap; i++)
		{
			for (j = i + gap; j < n; j += gap)
			{
				// 如果a[j] < a[j-gap],则寻找a[j]位置,
                            //并将后面数据的位置都后移。
				if (a[j] < a[j - gap])
				{
					int tmp = a[j];
					int k = j - gap;
					while (k >= 0 && a[k] > tmp)
					{
						a[k + gap] = a[k];
						k -= gap;
					}
					a[k + gap] = tmp;
				}
			}
		}

	}
}

/*
 * 对希尔排序中的单个组进行排序
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组总的长度
 *     i -- 组的起始位置
 *     gap -- 组的步长
 *
 *  组是"从i开始,将相隔gap长度的数都取出"所组成的!
 */
void group_sort(int a[], int n, int i, int gap)
{
	int j;

	for (j = i + gap; j < n; j += gap)
	{
		// 如果a[j] < a[j-gap],则寻找a[j]位置,并将后面数据的位置都后移。
		if (a[j] < a[j - gap])
		{
			int tmp = a[j];
			int k = j - gap;
			while (k >= 0 && a[k] > tmp)
			{
				a[k + gap] = a[k];
				k -= gap;
			}
			a[k + gap] = tmp;
		}
	}
}

/*
 * 希尔排序
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     n -- 数组的长度
 */
void shell_sort2(int a[], int n)
{
	int i, gap;

	// gap为步长,每次减为原来的一半。
	for (gap = n / 2; gap > 0; gap /= 2)
	{
		// 共gap个组,对每一组都执行直接插入排序
		for (i = 0; i < gap; i++)
			group_sort(a, n, i, gap);
	}
}

void main()
{
	int i;
	int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
	int ilen = LENGTH(a);

	printf("before sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");

	//shell_sort1(a, ilen);
	shell_sort2(a, ilen);

	printf("after  sort:");
	for (i = 0; i < ilen; i++)
		printf("%d ", a[i]);
	printf("\n");
	system("PAUSE");
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值