[数据结构]排序

本篇主要是对常见排序的分类和一些排序的详解

一、插入排序

1.直接插入排序

// 直接插入排序函数
void insertionSort(int arr[], int n) {
    int i, key, j;
    for (i = 1; i < n; i++) {
        key = arr[i]; // 取出待排序的元素
        j = i - 1;
        // 将大于key的元素向后移动一位
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        arr[j + 1] = key; // 将key插入到正确的位置
    }
}

这个排序的时间复杂度为O(n^2)空间复杂度为O(1)

2.希尔排序

#include <stdio.h>
// 希尔排序函数,接受一个整数数组和数组长度作为参数
void shell_sort(int arr[], int n) 
{
    // 外层循环控制不同的间隔值gap
    for (int gap = n / 3 + 1; gap > 0; gap /= 3) 
    {
        // 内层循环遍历数组中的元素,从索引gap开始到数组末尾
        for (int i = gap; i < n; i++) 
        {
            int temp = arr[i]; // 保存当前元素的值
            int j;
            // 比较当前元素与间隔为gap的前一个元素的大小,如果前一个元素较大,则交换它们的位置
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) 
            {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp; // 将保存的当前元素值赋给正确的位置
        }
    }
}

这个排序上回我们讲过了,空间复杂度为O(1),时间复杂度为O(n^1.3)。

二、选择排序

1.选择排序

// 选择排序函数
void selectionSort(int arr[], int n) {
    int i, j, minIndex, temp;
    for (i = 0; i < n - 1; i++) {
        minIndex = i; // 记录当前最小值的索引
        for (j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j; // 更新最小值索引
            }
        }
        // 交换当前元素与最小值元素
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

这个排序的思路也很简单,时间复杂度为O(N^2),空间复杂度是O(1)

2.堆排序

// 调整堆
void adjustHeap(int arr[], int i, int length) {
    int temp = arr[i]; // 取出当前元素
    for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
        if (k + 1 < length && arr[k] < arr[k + 1]) { // 如果右子节点大于左子节点,k指向右子节点
            k++;
        }
        if (arr[k] > temp) { // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
            arr[i] = arr[k];
            i = k;
        } else {
            break;
        }
    }
    arr[i] = temp; // 将temp值放到最终的位置
}

// 堆排序
void heapSort(int arr[], int length) {
    // 1.构建大顶堆
    for (int i = length / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, length);
    }
    // 2.调整堆结构+交换堆顶元素与末尾元素
    for (int j = length - 1; j > 0; j--) {
        // 交换堆顶元素和末尾元素
        int temp = arr[j];
        arr[j] = arr[0];
        arr[0] = temp;
        // 重新对堆进行调整
        adjustHeap(arr, 0, j);
    }
}

堆排序的时间复杂度O(N*logN),空间复杂度O(1)

三、交换排序

1,冒泡排序

// 冒泡排序函数
void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) { // 外层循环控制排序趟数
        for (int j = 0; j < n - 1 - i; j++) { // 内层循环控制每趟比较的次数
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换它们的位置
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

这个排序大家肯定都非常熟悉了,时间复杂度是O(n^2),空间复杂度是O(1)

2.快速排序

这个排序我们没讲过这里我们来讲一讲。

快速排序的整体思路就是保证基准值的左边的数小于基准值,右边大于基准值。

首先我们拥有如下数组

我们先选定一个基准值,(这里我们讲最基础的)我们选择第一个数作为基准值。

然后我们先从右往左走找小于基准值的值

找到后我们从左往右找大于基准值的

然后交换两个值

接着我们再重复之前的操作,直到相遇

然后将相遇点和基准值交换

然后我们可以得到基准值的下标,然后最左端不变,以基准值的下标-1为最右端,以基准值的下标为最左端,最右端不变把原来的数据一分为二:

然后再分别进行快速排序,以此类推,直到所有递归都返回.

代码示例:

void QuickSort(int* a, int left, int right)
{
	// 区间只有一个值或者不存在就是最小子问题
	if (left >= right)
		return;

	int begin = left, end = right;

	int keyi = left;
	while (left < right)
	{
		// right先走,找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// left再走,找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	keyi = left;

	// [begin, keyi-1]keyi[keyi+1, end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

快速排序的空间复杂度O(logN)时间复杂度是O(N*logN)。有意思的是其实快速排序的时间复杂度不是最坏情况,最坏情况应该是O(N^2),但是出现的概率很小。其次我们可以通过各种方法来改进

举几个例子,就不展开讲了:

//三数取中
int GetMidi(int* a, int left, int right)
{
	int mid = (left + right) / 2;


	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

我们通过三数取中得到key

在快排开头加上这个,就可以很好的减少我们出现最坏情况

int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);

如果觉得三数取中比较复杂,我们可以去随机数作为key,虽然效率不高,但是写起来方便,也能减少出现最坏情况

int randi = rand() % (right - left + 1);
randi += left;
Swap(&a[left], &a[randi]);

快速排序其实分好几种,这里我再展示一种前后指针法,个人感觉写起来来简单一点

void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
		return;

	int keyi = left;
	int prev = left;
	int cur = left+1;
	
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);
	keyi = prev;

	QuickSort2(a, left, keyi - 1);
	QuickSort2(a, keyi + 1, right);
}

这里再展示一下非递归的快速排序,感兴趣的可以自己看下(注意要结合栈)

void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{
 right = StackTop(&st);
 StackPop(&st);
 left = StackTop(&st);
 StackPop(&st);
 
 if(right - left <= 1)
 continue;
 int div = PartSort1(a, left, right);
 // 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)
 StackPush(&st, div+1);
 StackPush(&st, right);
 
 StackPush(&st, left);
 StackPush(&st, div);
}
 
 StackDestroy(&s);
}

四、归并排序

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 ,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
我将会把递归和非递归的写法展示给大家
void _MergeSort(int* a, int begin, int end, int* tmp)
{
    if (begin == end)
        return;

    int mid = (begin + end) / 2;
    _MergeSort(a, begin, mid, tmp);
    _MergeSort(a, mid+1, end, tmp);

    // 归并
    int begin1 = begin,end1 = mid;
    int begin2 = mid + 1,end2 = end;
    int i = begin;
    // 依次比较,取小的尾插tmp数组
    while (begin1 <= end1 && begin2 <= end2)
    {
        if (a[begin1] <= a[begin2])
        {
            tmp[i++] = a[begin1++];
        }
        else
        {
            tmp[i++] = a[begin2++];
        }
    }

    while (begin1 <= end1)
    {
        tmp[i++] = a[begin1++];
    }

    while (begin2 <= end2)
    {
        tmp[i++] = a[begin2++];
    }

    memcpy(a+begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

这部分代码实现了归并排序的递归版本。函数_MergeSort接受一个整数数组a、起始索引begin、结束索引end和一个临时数组tmp作为参数。首先判断是否只有一个元素,如果是则直接返回。然后计算中间索引mid,将数组分为两部分进行递归排序。接下来进行归并操作,将两个有序的子数组合并成一个有序的数组,并将结果存储在tmp数组中。最后将tmp数组的内容复制回原数组a

void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        perror("malloc fail");
        return;
    }

    _MergeSort(a, 0, n-1, tmp);

    free(tmp);
    tmp = NULL;
}

这部分代码是归并排序的入口函数,调用了递归版本的_MergeSort函数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后调用_MergeSort函数进行排序,最后释放临时数组的内存。

void MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        perror("malloc fail");
        return;
    }

    int gap = 1;

    while (gap < n)
    {
        for (int j = 0; j < n; j += 2 * gap)
        {
            int begin1 = j, end1 = begin1 + gap - 1;
            int begin2 = begin1 + gap, end2 = begin2 + gap - 1;

            if (end1 >= n || begin2 >= n)
                break;

            if (end2 >= n)
                end2 = n - 1;

            int i = j;
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (a[begin1] <= a[begin2])
                {
                    tmp[i++] = a[begin1++];
                }
                else
                {
                    tmp[i++] = a[begin2++];
                }
            }

            while (begin1 <= end1)
            {
                tmp[i++] = a[begin1++];
            }

            while (begin2 <= end2)
            {
                tmp[i++] = a[begin2++];
            }

            memcpy(a + j, tmp + j, sizeof(int) * (end2-j+1));
        }

        gap *= 2;
    }

    free(tmp);
    tmp = NULL;
}

这部分代码实现了归并排序的迭代版本。函数MergeSortNonR接受一个整数数组a和数组长度n作为参数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后使用迭代的方式逐步增大间隔gap,对数组进行分组归并排序。最后释放临时数组的内存


本篇不出意料,是初阶数据结构的最后一篇,下一篇我们要开始C++了。

相关推荐

  1. 数据结构--堆排序

    2024-04-02 05:56:03       41 阅读
  2. 数据结构-基数排序

    2024-04-02 05:56:03       62 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-02 05:56:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-02 05:56:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-02 05:56:03       82 阅读
  4. Python语言-面向对象

    2024-04-02 05:56:03       91 阅读

热门阅读