数据结构——排序

目录

1.排序的概念及其运用

        1.1排序的概念

        1.2常用的排序算法

2.常见排序算法的实现

   2.1 插入排序

        2.1.1插入排序的概念

2.1.1直接插入排序的实现

2.1.2直接插入排序的特性总结

2.2 希尔排序( 缩小增量排序 )

        2.2.1希尔排序的概念

2.2.2希尔排序的实现:

2.2.3希尔排序的特性总结:

2.3选择排序

        2.3.1选择排序的概念

        2.3.2选择排序的实现

2.3.3选择排序的特性总结

2.4堆排序

        2.4.1堆排序的概念

2.4.2堆排序的实现

 2.4.3堆排序的特性总结

2.5冒泡排序

        2.5.1冒泡排序的概念

        2.5.2冒泡排序的实现

        2.5.3冒泡排序的特性总结

2.6快速排序(快排)

        2.6.1快速排序的概念

        2.6.2快速排序的实现

(1)hoare版本

(2)挖坑法版本

(3)前后指针版本

 (4)快速排序-非递归版本

 2.6.3快速排序的特性总结

2.7归并排序

        2.7.1归并排序的概念

2.7.2归并排序的实现

2.7.3归并排序的特性总结

3.排序算法复杂度及稳定性分析

4.各排序代码:


1.排序的概念及其运用

        1.1排序的概念

    排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
    稳定性 :假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j] ,且 r[i] r[j] 之前,而在排序后的序列中, r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。
    内部排序 :数据元素全部放在内存中的排序。
    外部排序 :数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

        1.2常用的排序算法

        

2.常见排序算法的实现

   2.1 插入排序

        2.1.1插入排序的概念

    直接插入排 序是一种简单插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列
        就类似于打牌的时候码牌一样: 
         直接插入排序:
        当插入第i(i>=1) 个元素时,前面的   array[0]  ,  array[1]  ,…,  array[i-1]   已经排好序,此时用 array[i]   的排序码与  array[i-1]  ,  array[i-2]  ,…的排序码顺序进行比较,找到插入位置即将   array[i] 插入,原来位置上的元素顺序后移。
        如该图为例(递增排列):
第一趟:
 
这里有序,继续接着下一趟:
插入后形成

2.1.1直接插入排序的实现

 //插入排序
void InsertSort(int* a, int n)
{
    for(int j=0;j<n-1;j++)     //循环躺数;
    {
        int end=j;         //记录一下当前数组下标
        int tmp = a[end + 1];      //记录tmp用于比较大小,
        while (end >= 0)          //循环条件
        {
            if (tmp < a[end])        //比tmp小的话就需要更改了
            {
                a[end + 1] = a[end];        //比tmp小。更改一下位置,
                end--;         //别忘了end自减一下;
            }
            else       //走else就表明tmp不需要更改了直接跳出while循环
             {   
                break;
             }
        }
        a[end+1] = tmp;     //while循环结束后,end指向着是前一个位置,注意!需要+1
    }
}

2.1.2直接插入排序的特性总结

1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度: O(N^2)
3. 空间复杂度: O(1) ,它是一种稳定的排序算法
4. 稳定性:稳定

2.2 希尔排序( 缩小增量排序 )

        2.2.1希尔排序的概念

        希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工 作。当到达 =1 时,所有记录在统一组内排好序

2.2.2希尔排序的实现:

void ShellSort(int* a, int n)
{
    int gap = n;
    // gap > 1时是预排序,目的让他接近有序
    // gap == 1是直接插入排序,目的是让他有序
    while (gap > 1)
    {
        //gap = gap / 2;
        gap = gap / 3 + 1;

        for (int i = 0; i < n - gap; ++i)
        {
            int end = i;
            int tmp = a[end + gap];
            while (end >= 0)
            {
                if (tmp < a[end])
                {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                else
                {
                    break;
                }
            }
            a[end + gap] = tmp;
        }
    }
}

2.2.3希尔排序的特性总结:

        1. 希尔排序是对直接插入排序的优化。
        2. 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
        3. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:
        《数据结构 (C 语言版 ) --- 严蔚敏:
        《数据结构- 用面相对象方法与 C++ 描述》 --- 殷人昆:
        4. 稳定性:不稳定

2.3选择排序

        2.3.1选择排序的概念

        每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直            到全部待排序的数据元素排完 。
        1.在元素集合array[i]--array[n-1] 中选择关键码最大 ( ) 的数据元素。
        2.若它不是这组元素中的最后一个 ( 第一个 )元素,则将它与这组元素中的最后一个(第一个)             元素交换。
        3.在剩余的 array[i]--array[n-2] array[i+1]--array[n-1] )集合中,重复上述步骤,直到集合剩               余 1 个元素

        2.3.2选择排序的实现

// 选择排序   

//正常的选择排序是一次遍历寻找一个最大数或最小数,这里我直接一次遍历找到最大和最小数进行交换:           
void SelectSort(int* a, int n)
{
    int begin = 0, end = n - 1;      //记录数组下标
    while(begin<end){
        int min = begin; int max = begin;     //定义两个数,分别为最大和最小
        for (int i = begin + 1; i <= end; i++)    //循环遍历,找到最小和最大的数
        {
            if (a[i] < a[min])
                min = i;
            if (a[i] > a[max])
                max = i;
        }
        Swap(&a[begin], &a[min]);     //把最小的数的下标和第一位交换
        if (max == begin)       //因为我是直接一遍遍历找到最小和最大值,
        {                   //防止他们交换的时候值会有重叠,不然会出错;
            max = min;
        }
        Swap(&a[end], &a[max]);  //把最大的数的下标和最后一位交换          
        begin++; end--;
    }
}

2.3.3选择排序的特性总结

        1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
        2. 时间复杂度: O(N^2)
        3. 空间复杂度: O(1)
        4. 稳定性:不稳定

2.4堆排序

        2.4.1堆排序的概念

        堆排序(Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

2.4.2堆排序的实现

// 堆排序
void AdjustDwon(int* a, int n, int root)  //向下调整:
{
        int cur = root;
        int pret = cur * 2 + 1;
        while (pret < n)
        {
            if (pret + 1 < n && a[pret] > a[pret + 1])
            {
                pret++;
            }
            if (a[cur] > a[pret])
            {
                Swap(&a[cur], &a[pret]);
                cur = pret;
                pret = pret * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }

void HeapSort(int* a, int n)
{
    //大堆
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDwon(a, n, i);
    }
    int end = n - 1;
    while (end > 0)
    {
        Swap(&a[0], &a[end]);   //这个为交换值的函数
        AdjustDwon(a, end, 0);
        --end;
    }
}

 2.4.3堆排序的特性总结

        1. 堆排序使用堆来
        2. 时间复杂度:O(N*logN)
        3. 空间复杂度:O(1)
        4. 稳定性:不稳定

2.5冒泡排序

        2.5.1冒泡排序的概念

        基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

        2.5.2冒泡排序的实现

// 冒泡排序
void BubbleSort(int* a, int n)                        
{
    for (int j = 0; j < n; j++)                                
    {
        for (int i = 0; i < n - 1 - j; i++)
        {
            if (a[i] > a[i + 1])
            {
                Swap(&a[i + 1], &a[i]);
            }
        }
    }
}

        2.5.3冒泡排序的特性总结

        1. 冒泡排序是一种非常容易理解的排序
        2. 时间复杂度: O(N^2)
        3. 空间复杂度: O(1)
        4. 稳定性:稳定

2.6快速排序(快排)

        2.6.1快速排序的概念

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

例:

        2.6.2快速排序的实现

        快排的实现我写了三种写法,实现的过程各不一样,但结果是一样的。

(1)hoare版本
// 快速排序hoare版本
void PartSort(int* a, int begin, int end)    //begin为数组区间首元素的下标
{                                            //end为数组区间尾元素的下标

    if (begin >= end)
        return;



    int key = begin;
    int left = begin, right = end;

    while (left < right)
    {
        //右边找小
        while (left < right && a[right] >= a[key])
        {
            --right;
        }
        //左边找大
        while (left < right && a[left] <= a[key])
        {
            ++left;
        }
        Swap(&a[right], &a[left]);
    }
    Swap(&a[left], &a[key]);
    return left;



       PartSort(a, begin, left - 1);        //递归左边的区间进行排序
       PartSort(a, left + 1, end);        //递归右边的区间进行排序
}
(2)挖坑法版本
// 快速排序   挖坑法
void PartSort(int* a, int begin, int end)
{        

           if (begin >= end)
                return;

    int key = a[begin];
    int insr = begin;
    int left = begin, right = end;
    while (left < right)
    {
        //找小
        while (left < right && a[right] >= key)
        {
            right--;
        }
        a[insr] = a[right];
        insr = right;
        //找大
        while (left < right && a[left] < key)
        {
            left++;
        }
        a[insr] = a[left];
        insr = left;
    }
    a[insr] = key;

       PartSort(a, begin, left - 1);        //递归左边的区间进行排序
       PartSort(a, left + 1, end);        //递归右边的区间进行排序
}
(3)前后指针版本
void PartSort(int* a, int begin, int end)
{

    int prev = begin;
    int key = begin;
    int cur = prev + 1;
    while (cur <= end)
    {
        if (a[cur] < a[key])
        {
            prev++;
            Swap(&a[cur], &a[prev]);
        }
        cur++;
    }
    Swap(&a[key], &a[prev]);

       PartSort(a, begin, prev - 1);        //递归左边的区间进行排序
       PartSort(a, prev + 1, end);        //递归右边的区间进行排序
}
 (4)快速排序-非递归版本

        非递归版本的快排,需要利用栈来实现,虽说不是函数递归,但是实现过程也类似递归。

// 快速排序前后指针法  也可以用挖坑法或者hoare版本都可以
int PartSort(int* a, int begin, int end)
{
	int prei = GetMidi(a, begin, end);          // GetMidi这个函数是找中位数,进行快排优化的      Swap(&a[prei], &a[begin]);                 //返回 a[begin]-a[(begin+end)/2]-a[end] 
          
	int prev = begin;                          //a数组中的中位数   即中间值;
	int key = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[key])
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[key], &a[prev]);
	return prev;
}
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	//利用栈后入先出的特性  
	//模仿递归   把左右区间的值插入栈(注意顺序) 拿一个区间出来,压两个区间进去,直           到栈里没有数据
	Stack s;                                //   创建一个stack(栈)的变量;
	StackInit(&s);                         //    栈——初始化
	StackPush(&s, right);                 //先把右区间的坐标入栈 (注意栈的特性:先进后出!)
	StackPush(&s, left);                 //再把左区间的坐标入栈   

	while (!STEmpty(&s))               //判断,当栈内没有元素的时候退出循环
	{
		int begin = StackTop(&s);    // StackTop:返回当前栈顶元素
		StackPop(&s);               //   StackPop:栈顶出栈
		int end = StackTop(&s);
		StackPop(&s);

		int cur = PartSort(a, begin, end);
		if (begin < cur - 1)   //当begin<cur-1的时候 再把这里面区间的坐标入栈
		{
			StackPush(&s, cur - 1);
			StackPush(&s, begin);
		}
		if (cur + 1 < end)
		{
			StackPush(&s, end);
			StackPush(&s, cur + 1);
		}

	}

	StackDestroy(&s);//栈的销毁
}

 2.6.3快速排序的特性总结

        1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫 快速 排序
        2. 时间复杂度: O(N*logN)
        3. 空间复杂度: O(logN)
        4. 稳定性:不稳定

2.7归并排序

        2.7.1归并排序的概念

基本思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

2.7.2归并排序的实现

void _MergeSort(int* a, int* tmp,int begin ,int end )
{
	if (begin >= end)
		return;
	int cur = (begin + end) / 2;
	//左右区间
	_MergeSort(a, tmp, begin, cur);
	_MergeSort(a, tmp, cur + 1, end);

	//交换
	int beg1 = begin, end1 = cur;
	int beg2 = cur + 1, end2 = end;
	int i = begin;
	while (beg1<=end1&&beg2<=end2)
	{
		if (a[beg1] > a[beg2])
			tmp[i++] = a[beg2++];
		else
			tmp[i++] = a[beg1++];
	}
	while(beg1<=end1)
	{
		tmp[i++] = a[beg1++];
	}
	while (beg2 <= end2)
	{
		tmp[i++] = a[beg2++];
	}

	//拷贝数组
	memcpy(a+begin, tmp+begin, sizeof(int) * (end-begin+1));
}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("mallocc error...\n");
		exit(-1);
	}
		_MergeSort(a, tmp, 0, n - 1);
		//memcpy(n, tmp, sizeof(int) * n);
		free(tmp);
}

2.7.3归并排序的特性总结

        1. 归并的缺点在于需要 O(N) 的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序              问题。
        2. 时间复杂度: O(N*logN)
        3. 空间复杂度: O(N)
        4. 稳定性:稳定

3.排序算法复杂度及稳定性分析

4.各排序代码:

#define _CRT_SECURE_NO_WARNINGS
#include"sort.h"
void Swap(int* q, int* p)
{
	int tmp = *q;
	*q = *p;
	*p = tmp;
}

void print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}


 //插入排序
void InsertSort(int* a, int n)
{
	for(int j=0;j<n-1;j++)
	{
		int end=j;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
				break;
		}
		a[end+1] = tmp;
	}
}

// 希尔排序
//void ShellSort(int* a, int n)
//{
//	int gap = n;
//	//for(int j=0;j<gap;j++)
//	//{
//	while(gap>1){
//		//gap = gap / 2;
//		gap = gap / 3+1;
//		for (int i = 0; i < n - gap; ++i)
//		{
//			int end = i;
//			int tmp = a[end + gap];
//			while (end >= 0)
//			{
//				if (tmp < a[end])
//				{
//					a[end + gap] = a[end];
//					end -= gap;
//				}
//				else
//				{
//					break;
//				}
//			}
//			a[end + gap] = tmp;
//		}
//	}
//	//}
//	
//}
void ShellSort(int* a, int n)
{
	int gap = n;
	// gap > 1时是预排序,目的让他接近有序
	// gap == 1是直接插入排序,目的是让他有序
	while (gap > 1)
	{
		//gap = gap / 2;
		gap = gap / 3 + 1;

		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

// 选择排序
void SelectSort(int* a, int n)
{
	//变形
	int begin = 0, end = n - 1;
	
	while(begin<end){
		int min = begin; int max = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[min])
				min = i;
			if (a[i] > a[max])
				max = i;
		}
		Swap(&a[begin], &a[min]);
		if (max == begin)
		{
			max = min;
		}
		Swap(&a[end], &a[max]);
		begin++; end--;
	}
}
// 堆排序
void AdjustDwon(int* a, int n, int root)
{
		int cur = root;
		int pret = cur * 2 + 1;
		while (pret < n)
		{
			if (pret + 1 < n && a[pret] > a[pret + 1])
			{
				pret++;
			}
			if (a[cur] > a[pret])
			{
				Swap(&a[cur], &a[pret]);
				cur = pret;
				pret = pret * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
void HeapSort(int* a, int n)
{
	//大堆
	//o(n)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		--end;
	}
}

// 冒泡排序
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		for (int i = 0; i < n - 1 - j; i++)
		{
			if (a[i] > a[i + 1])
			{
				Swap(&a[i + 1], &a[i]);
			}
		}
	}
}

// 快速排序递归实现
// 
// //返回中位数
int GetMidi(int* a, int left, int end)
{
	int set = (left + end) / 2;
	if (a[left] < a[set])
	{
		if (a[set] < a[end])
			return set;
		else if (a[left] > a[end])
			return left;
		else
			return end;
	}
	else
	{
		if (a[set] > a[end])
			return set;
		else if (a[end] > left)
			return left;
		else
			return end;
	}
}
// 快速排序hoare版本
int PartSort1(int* a, int begin, int end)
{
	int prei = GetMidi(a, begin, end);
	Swap(&a[prei], &a[begin]);

	int key = begin;
	int left = begin, right = end;

	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= a[key])
		{
			--right;
		}
		//左边找大
		while (left < right && a[left] <= a[key])
		{
			++left;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[key]);
	return left;
}
// 快速排序   挖坑法
int PartSort2(int* a, int begin, int end)
{
	int prei = GetMidi(a, begin, end);
	Swap(&a[prei], &a[begin]);

	int key = a[begin];
	int insr = begin;
	int left = begin, right = end;
	while (left < right)
	{
		//找小
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[insr] = a[right];
		insr = right;
		//找大
		while (left < right && a[left] < key)
		{
			left++;
		}
		a[insr] = a[left];
		insr = left;
	}
	a[insr] = key;
	return left;
}
// 快速排序前后指针法
int PartSort3(int* a, int begin, int end)
{
	int prei = GetMidi(a, begin, end);
	Swap(&a[prei], &a[begin]);

	int prev = begin;
	int key = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[key])
		{
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[key], &a[prev]);
	return prev;
}

//void QuickSort(int* a, int left, int right)
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int prei = PartSort3(a, begin, end);
	QuickSort(a, begin, prei - 1);
	QuickSort(a, prei + 1, end);
}
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	//利用栈后入先出的特性  
	//模仿递归   把左右区间的值插入栈(注意顺序) 拿一个区间出来,压两个区间进去,直到栈里没有数据
	Stack s;
	StackInit(&s);
	StackPush(&s, right);
	StackPush(&s, left);

	while (!STEmpty(&s))
	{
		int begin = StackTop(&s);
		StackPop(&s);
		int end = StackTop(&s);
		StackPop(&s);

		int cur = PartSort3(a, begin, end);

		//left     cur     right 
		if(begin<cur-1)
		{
		StackPush(&s,cur-1);
		StackPush(&s,begin); 
		}
		if(cur+1<end)
		{
		StackPush(&s,end);
		StackPush(&s,cur+1);
		}
	
	}

	StackDestroy(&s);
}



void _MergeSort(int* a, int* tmp,int begin ,int end )
{
	if (begin >= end)
		return;
	int cur = (begin + end) / 2;
	//左右区间
	_MergeSort(a, tmp, begin, cur);
	_MergeSort(a, tmp, cur + 1, end);

	//交换
	int beg1 = begin, end1 = cur;
	int beg2 = cur + 1, end2 = end;
	int i = begin;
	while (beg1<=end1&&beg2<=end2)
	{
		if (a[beg1] > a[beg2])
			tmp[i++] = a[beg2++];
		else
			tmp[i++] = a[beg1++];
	}
	while(beg1<=end1)
	{
		tmp[i++] = a[beg1++];
	}
	while (beg2 <= end2)
	{
		tmp[i++] = a[beg2++];
	}

	//拷贝数组
	memcpy(a+begin, tmp+begin, sizeof(int) * (end-begin+1));
}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("mallocc error...\n");
		exit(-1);
	}
		_MergeSort(a, tmp, 0, n - 1);
		//memcpy(n, tmp, sizeof(int) * n);
		free(tmp);
}
// 归并排序非递归实现
void MergeSortNonR(int* a, int n);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值