数据结构——各种常见算法的实现方法和思路

news2024/11/22 23:10:16

文章目录

  • 常见的排序算法
    • 类型
    • 复杂度和稳定性
  • 1.冒泡排序
  • 2.直接插入排序
  • 3.希尔排序
  • 4.简单选择排序
    • 方法1:双向遍历选择排序
    • 方法2:单向遍历选择排序
  • 5.归并排序
    • 方法1:递归
    • 方法2:非递归
  • 6.快速排序
    • 方法1:随机取keyi
    • 方法2:三数取中
    • 方法3:挖坑法
    • 方法4:前后指针法
    • 方法5:非递归
    • 方法6:三路划分
  • 7.堆排序
  • 8.计数排序
  • 9.基数排序
  • 9.桶排序

在这里插入图片描述

常见的排序算法

类型

在这里插入图片描述

复杂度和稳定性

数据结构的复杂度和稳定性是评价数据结构优劣的两个方面,复杂度主要关注数据结构在执行操作时所需的时间和空间资源消耗,而稳定性主要关注数据结构在执行操作时是否能够保证原有数据的相对位置不变。

在这里插入图片描述

1.冒泡排序

请添加图片描述
请添加图片描述

这是一个冒泡排序算法的实现,它可以对一个整型数组按照从大到小的顺序进行排序。

算法的基本思想是通过不断比较相邻的元素并交换它们的位置,将较大的元素逐渐“冒泡”到数组的顶端。具体来说,该算法通过双重循环实现:外层循环控制比较的轮数,内层循环负责相邻元素的比较和交换操作。在每一轮比较中,如果当前元素比它后面的元素小,就交换它们的位置,使得较大的元素逐渐向数组的前部移动。

该算法的时间复杂度为O(n^2),其中n为数组的长度。虽然它的时间复杂度较高,但是它的实现简单、容易理解,对于小规模的数据排序来说还是比较实用的。

//从小到大
void BubbleSort1(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

这也是一个冒泡排序算法的实现,可以对一个整型数组按照从小到大的顺序进行排序。

与上一个算法相比,该算法的唯一区别在于内层循环中比较的方式。在该算法中,如果当前元素比它后面的元素大,就交换它们的位置,使得较小的元素逐渐向数组的前部移动。

该算法的时间复杂度同样为O(n^2),其中n为数组的长度。由于冒泡排序算法的优化空间比较有限,因此它的时间复杂度较高,不适合用于大规模数据的排序。

//从大到小
void BubbleSort2(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] < a[j + 1])
			{
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

2.直接插入排序

在这里插入图片描述
插入排序是一种简单直观的排序算法,其核心思想是将未排序的元素逐个插入到已排序的序列中,以便生成一个新的有序序列。插入排序的过程类似于打扑克牌时的整理牌的方法,即将一张牌插入到已经排好序的牌中的适当位置。

插入排序的基本思路是从第二个元素开始,逐个将元素插入到前面已经排好序的子序列中。具体而言,对于第 i 个元素,我们将其与前面的元素逐个比较,找到第一个比它小的元素,然后将它插入到这个元素后面,即可保证前 i 个元素已经排好序。这个过程不断重复,直到整个序列都有序为止。

插入排序的时间复杂度为 O(n^2),它的性能与输入数据的初始顺序有关。如果输入数据已经接近有序,那么插入排序的效率会比较高,因为它只需要进行少量的比较和移动操作。但如果输入数据的顺序比较随机,那么插入排序的效率会比较低,因为它需要进行大量的比较和移动操作。


// 插入排序
void InsertSort(int* a, int n)
{
     //从第2个元素开始插入排序
    for (int i = 1; i < n; i++)
    {
        int end = i - 1;  // 已排序序列的最后一个元素的下标
        int tmp = a[i];     // 待插入的元素
         //将待插入的元素与已排序的元素从后往前依次比较
        while (end >= 0)
        {
            if (tmp < a[end])
            {
                a[end + 1] = a[end];  // 如果比已排序的元素小,则将已排序的元素后移一位
                end--;
            }
            else
            {
                break;  // 如果找到一个比待插入元素小的位置,则退出循环
            }
        }
        a[end + 1] = tmp;  // 将待插入的元素插入到已排序的序列中
    }
}

3.希尔排序

在这里插入图片描述
希尔排序(Shell Sort)是插入排序的一种改进版本,也称为缩小增量排序(diminishing increment sort)。它通过将待排序的序列分割成若干个子序列,对每个子序列进行插入排序,从而实现对整个序列的排序。

希尔排序的核心思想是将相距某个“增量”的元素组成一个子序列,对每个子序列进行插入排序,使得整个序列在增量不断缩小的情况下逐步变得有序。最后当增量为1时,整个序列就变成了一个有序序列。

具体实现中,希尔排序先将待排序的元素按照一定的增量分成若干个子序列,对每个子序列进行插入排序。然后,逐渐减小增量,继续对子序列进行插入排序,直到增量为1时,完成最后一次排序,整个序列就变成了有序序列。

希尔排序的时间复杂度为 O(n log n) 到 O(n^2),具体取决于增量的选择和子序列的划分方式。希尔排序的优点是它的实现比较简单,只需要对插入排序进行一些改进即可。缺点是增量序列的选择比较困难,不同的增量序列对性能的影响也比较大。

//希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[i + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

4.简单选择排序

在这里插入图片描述

选择排序(Selection Sort)是一种简单直观的排序算法,其核心思想是在未排序序列中选择最小(或最大)的元素,放到已排序序列的末尾,直到所有元素都排完为止。

具体实现中,选择排序从未排序序列中找到最小(或最大)的元素,然后将它与未排序序列的第一个元素交换,这样就可以把该元素放到已排序序列的末尾。然后,在剩余的未排序序列中继续找到最小(或最大)的元素,重复上述操作,直到所有元素都排完为止。

选择排序的时间复杂度为 O(n^2),它的性能比冒泡排序略好,但比插入排序差。选择排序的优点是它的实现比较简单,只需要进行 n-1 次比较和 n 次交换,因此在某些情况下它的性能可能比其他排序算法更好。缺点是它的时间复杂度比较高,不适合对大规模数据进行排序。

方法1:双向遍历选择排序

//交换
Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void SelectSort1(int* a, int n) 
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int max = left, mini = left;
		for (int i = left + 1; i <= right; i++)
		{
			
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[max])
			{
				max = i;
			}
		}
		Swap(&a[left], &a[mini]);
		if (left == max)
		{
			max = mini;
		}
		Swap(&a[right], &a[max]);
		++left;
		--right;
	}
}

方法2:单向遍历选择排序

//交换
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void SelectSort2(int* a, int n)
{
	int min,i,j;
	for (int i = 0; i < n - 1; i++)
	{
		min = i;
		for (int j = i + 1; j < n; j++)
		{
			if (a[j] < a[min])
			{
				min = j;
			}
		}
		int temp = a[min];
		a[min] = a[i];
		a[i] = temp;
    }
}

5.归并排序

在这里插入图片描述

方法1:递归

这是一种基于分治的排序算法 - 归并排序。
思路:

  1. 将数组分成两个子数组,递归调用排序子数组
  2. 将两个有序子数组合并成一个有序数组
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin>=end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	//[begin,mid] [mid+1,end],子区间递归排序
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	//[begin,mid] [mid+1,end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	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));
}
void MergeSort(int* a, int n)
{
	
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("mallco fail");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

方法2:非递归

思路:

  1. 使用一个gap进行划分,从1开始扩大gap,每次将相邻gap大小的两个子数组合并
  2. 合并相邻gap大小的两个子数组的方法和递归版本是一样的
  3. 每次将gap * 2,进行下一轮划分和合并
  4. 重复此过程,直到gap >= n,排序完成
//非递归
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("mallco fail");
		return;
	}
	int gap = 1;
	while (gap<n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//[begin1,end1][begin2,end2]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//归并一部分拷贝一部分
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

6.快速排序

在这里插入图片描述

方法1:随机取keyi

思路:

  1. 选择一个基准元素(使用随机数随机选择一个元素)
  2. 将所有比基准元素小的元素移动到其左边,所有比基准元素大的元素移动到其右边
  3. 对基准元素的左右两边重复第一步和第二步,直到全部排序完成
void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	//随机选key
	int randi = left + (rand() % (right - left));
	Swap(&a[left], &a[randi]);
	int keyi = left;
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= a[keyi])
			--right;
		//左边找大
		while (left<right && a[left]>a[keyi])
			++left;
		Swap(&a[left], &a[right]);
	}
}

方法2:三数取中

思路:

  1. 首先利用中位数作为基准值,选择最稳定的基准值。
  2. 获取三个数的中位数:
    如果a[left]<a[mid],那么:如果a[mid]<a[right], mid是中位数,否则比较a[left] 和 a[right],较大值是中位数
  3. 将中位数与第一个元素交换,作为基准值
  4. 其他部分与普通快速排序相同
void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
int GetMidNumi(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//a[left]>a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
void QuickSort2(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
	{
		Swap(&a[midi], &a[left]);
	}
	int keyi = left;
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= a[keyi])
			--right;
		//左边找大
		while (left<right && a[left]<=a[keyi])
			++left;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	keyi = left;
	QuickSort2(a, begin, keyi - 1);
	QuickSort2(a, keyi + 1, end);
}

方法3:挖坑法

在这里插入图片描述

思路:

  1. 使用 begin 和 end 表示子数组范围
  2. key 记录基准值,hole 记录待填充位置
  3. 把 left 和 right 指针分别指向 begin 和 end
  4. while 循环:右边第一个小于等于 key 的元素,记录其索引到 hole, right,左边第一个大于 key 的元素,记录其索引到 hole,left++
  5. hole 位置填充 key 值
  6. 对基准值左右两侧的子数组递归调用快速排序
void QuickSort3(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= key)
			--right;
		a[hole] = a[right];
		hole = right;
		//左边找大
		while (left < right && a[left] <= key)
			++left;
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	QuickSort3(a, begin, hole - 1);
	QuickSort3(a, hole+ 1, end);
}

方法4:前后指针法

在这里插入图片描述

快速排序的前后指针法(也称为双指针法)是一种常见的划分方式。它的基本思想是,将整个序列分为小于等于主元的左半部分和大于主元的右半部分,然后递归地对左右两个部分进行排序。

思路:

  1. 依然使用left、right指针和基准值pivot。
  2. 额外引入一个prev指针,记录小于pivot的最后一个元素的位置。
  3. 当遍历到一个元素时:如果元素 < pivot,将其与prev指向的元素交换,prev后移 1 位,如果元素 = pivot,直接略过,如果元素 > pivot,继续遍历
  4. 最终prev指向的位置,就是pivot的正确位置。
int QuickSort4(int* a, int left, int right)
{
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
	{
		Swap(&a[midi], &a[left]);
	}
	int keyi =left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] <a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);
		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

方法5:非递归

这里使用了一个栈来模拟递归过程。首先将整个序列的左右端点入栈,之后每次取出栈顶的左右端点,进行一次划分并将新的左右端点入栈,直到栈为空为止。在划分函数中使用双指针法将小于等于主元的元素放在左边,大于主元的元素放在右边,最后把主元放在中间,并返回主元的下标。

需要注意的是,这里使用了一个结构体 Range 来表示左右端点的范围,这样可以方便地把左右端点打包在一起并压入栈中。

#include <stdio.h>
#define MAX_SIZE 100
typedef struct {
    int l;
    int r;
} Range;

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int l, int r) {
    int pivot = arr[l];
    while (l < r) {
        while (l < r && arr[r] >= pivot) r--;
        arr[l] = arr[r];
        while (l < r && arr[l] <= pivot) l++;
        arr[r] = arr[l];
    }
    arr[l] = pivot;
    return l;
}

void quickSort(int arr[], int n) {
    Range stack[MAX_SIZE];
    int top = -1;
    stack[++top] = (Range){ 0, n - 1 };
    while (top >= 0) {
        Range range = stack[top--];
        if (range.l >= range.r) continue;
        int p = partition(arr, range.l, range.r);
        stack[++top] = (Range){ range.l, p - 1 };
        stack[++top] = (Range){ p + 1, range.r };
    }
}

int main() {
    int arr[] = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
    int n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

方法6:三路划分

在这里插入图片描述

思路:

  1. 跟key相等的值,往后推
  2. 比key小的甩到左边
  3. 比key大的甩到右边
  4. 跟key相等的就在中间
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
void quicksort(int arr[], int low, int high) {
    if (low >= high) {
        return;
    }
    // 选取第一个元素为基准值
    int pivot = arr[low];
    // lt指向小于基准值的部分的最后一个元素
    int lt = low;
    // gt指向大于基准值的部分的第一个元素
    int gt = high;
    // i指向当前处理的元素
    int i = low + 1;
    // 三路划分
    while (i <= gt) {
        if (arr[i] < pivot) {
            // 将小于基准值的元素交换到lt部分
            swap(&arr[i], &arr[lt]);
            lt++;
            i++;
        }
        else if (arr[i] > pivot) {
            // 将大于基准值的元素交换到gt部分
            swap(&arr[i], &arr[gt]);
            gt--;
        }
        else {
            // 相等的元素直接跳过
            i++;
        }
    }
    // 递归排序小于基准值的部分
    quicksort(arr, low, lt - 1);
    // 递归排序大于基准值的部分
    quicksort(arr, gt + 1, high);
}
int main() {
    int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    quicksort(arr, 0,n-1);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

7.堆排序

在这里插入图片描述

思路:

使用 maxHeapify() 函数维护最大堆的属性。

使用 n/2 - 1 作为起始索引,逐层地将数组调整为最大堆。

排序部分:

  1. 将堆顶元素(最大元素)与末尾元素交换
  2. 缩小堆的范围(n范围减1),再次调用 maxHeapify() 函数,维护最大堆的属性。
  3. 重复上两步,直到堆范围为0。
// 交换函数 
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void maxHeapify(int arr[], int n, int i) {
    int largest = i;
    int left = 2*i + 1;
    int right = 2*i + 2;

    if (left < n && arr[left] > arr[largest])
        largest = left;

    if (right < n && arr[right] > arr[largest])
        largest = right;

    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        maxHeapify(arr, n, largest);
    }
}

void heapSort(int arr[], int n) {
    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        maxHeapify(arr, n, i);

    // 排序
    for (int i = n - 1; i >= 0; i--) {
        swap(&arr[0], &arr[i]); // 将最大值交换到数组末尾
        maxHeapify(arr, i, 0);
    }
}

int main() {
    int arr[] = { 12, 11, 13, 5, 6, 7 };
    int n = sizeof(arr) / sizeof(arr[0]);

    heapSort(arr, n);

    // 打印排序后的数组
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

8.计数排序

请添加图片描述

思路:

  1. 扫描一次给定的数组,找到最大元素和最小元素
  2. 根据最大元素和最小元素,计算出需要多大的空间用于排序
  3. 创建计数数组 countA,长度为范围(max - min + 1)
  4. 遍历给定数组,对于每个元素 a[i],计数数组 countA[a[i] - min] 加 1
  5. 对计数数组进行累加求和,使每个元素表示当前元素及之前所有元素的个数
  6. 遍历给定数组,将元素放在正确位置上
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* countA = (int*)malloc(sizeof(int) * range);
	if (countA == NULL)
	{
		perror("malloc fail");
		return;
	}
	memset(countA, 0, sizeof(int) * range);

	//计数
	for (int i = 0; i < n; i++)
	{
		countA[a[i] - min]++;
	}

	//排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (countA[i]--)
		{
			a[j++] = i + min;
		}
	}
	free(countA);
}
int main()
{
	int arr[] = { 2,3,5,6,7,3,2,5,5,5,9,200 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	CountSort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

9.基数排序

请添加图片描述

思路:

  1. 对数组按照最低阶(个位数)进行分配,然后收集。
  2. 再按照次低阶(十位数)分配和收集,依次类推到最高阶。
  3. 每次分配、收集都会让数组更加有序。

具体实现:

  1. 定义最大基数为 RADIX(10),一个辅助队列组 Q[]
  2. Getkey() 函数获取元素 value 在指定位数 k 的值
  3. Distribute() 函数将数组中元素按照 k 位分配到相应的队列中
  4. Collect() 函数收集队列中的元素,反向填充数组
  5. RadixSort() 函数依次分配、收集 K次,K为指定位数
#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
#define K 3
#define RADIX 10
queue<int> Q[RADIX];

int Getkey(int value, int k)
{
	int key = 0;
	while (k >= 0)
	{
		key = value % 10;
		value /= 10;
		k--;
	}
	return key;
}
//分发数据
void  Distribute(int arr[], int left, int right, int k)
{
	for (int i = left; i < right; i++)
	{
		int key = Getkey(arr[i],k);
		Q[key].push(arr[i]);
	}
}
//回收数据
void Collect(int arr[])
{
	int k = 0;
	for (int i = 0; i < RADIX; i++)
	{
		while (!Q[i].empty())
		{
			arr[k++] = Q[i].front();
			Q[i].pop();
		}
	}
}
void RadixSort(int* arr, int left, int right)
{
	for (int i = 0; i < K; i++)
	{
		//分发数据
		Distribute(arr, left, right, i);
		//回收数据
		Collect(arr);
	}
}

int main()
{
	int arr[] = { 278,109,63,930,589,184,505,269,8,83 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	//基数排序
	RadixSort(arr, 0, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

9.桶排序

在这里插入图片描述

桶排序是一种线性排序算法,它的基本思想是将待排序的元素分到不同的桶中,每个桶内的元素再分别使用其他排序算法进行排序,最后合并所有桶中的元素即可得到有序序列。桶排序的时间复杂度为 O(n),但是它的空间复杂度较高,需要额外的空间来存储桶。

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

// 桶排序
void bucket_sort(int arr[], int n) {
    int max_num = arr[0];
    int min_num = arr[0];
    int i, j, k;
    for (i = 1; i < n; i++) {
        if (arr[i] > max_num) {
            max_num = arr[i];
        }
        if (arr[i] < min_num) {
            min_num = arr[i];
        }
    }

    int bucket_size = (max_num - min_num) / n + 1;
    int bucket_count = (max_num - min_num) / bucket_size + 1;
    int **buckets = malloc(sizeof(int*) * bucket_count);
    for (i = 0; i < bucket_count; i++) {
        buckets[i] = malloc(sizeof(int) * n);
    }
    int *count = malloc(sizeof(int) * bucket_count);

    for (i = 0; i < bucket_count; i++) {
        count[i] = 0;
    }
    for (i = 0; i < n; i++) {
        int index = (arr[i] - min_num) / bucket_size;
        buckets[index][count[index]] = arr[i];
        count[index]++;
    }

    k = 0;
    for (i = 0; i < bucket_count; i++) {
        for (j = 0; j < count[i]; j++) {
            arr[k] = buckets[i][j];
            k++;
        }
    }

    for (i = 0; i < bucket_count; i++) {
        free(buckets[i]);
    }
    free(buckets);
    free(count);
}

// 测试桶排序
int main() {
    int arr[] = {5, 2, 8, 3, 9, 6};
    int n = sizeof(arr) / sizeof(int);
    bucket_sort(arr, n);
    printf("排序后的结果为:");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/754835.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GD32F303 DAM串口接收

1.设置串口 串口配置比较常规&#xff0c;我只应用的空闲中断。 2.DMA设置 我设置的DMA是串口接收到数据后保存到数组里&#xff0c;数组满了以后会自动从头开始&#xff0c;并且会进入一次DMA中断。

Jenkins+Robot 接口自动化测试

目录 前言&#xff1a; 设计目标 项目说明 目录结构 配置 jenkins 1.安装插件 2.配置项目 前言&#xff1a; JenkinsRobot是一种常见的接口自动化测试方案&#xff0c;可以实现自动化的接口测试和持续集成。Jenkins是一个流行的持续集成工具&#xff0c;而Robot Framew…

代码随想录day3 | 203.移除链表元素 707.设计链表 206.反转链表

文章目录 一、移除链表元素的思想两种方法 二、203.移除链表元素三、707.设计链表四、206.反转链表 一、移除链表元素的思想 直接让前一个节点指向后一个节点即可 两种方法 第一种&#xff1a;直接删除 第二种&#xff1a;头删的时候&#xff0c;直接headhead->next 其实…

JAVA数据结构、集合操作及常用API_C++开发转JAVA

文章目录 零、引言一、JAVA数据结构基础1.0 数据类型概述1.1 基本数据类型 零、引言一、JAVA数据结构基础1.0 数据类型概述1.1 基本数据类型1.2 包装类1.3 基本类型和包装类型的区别1.4 包装类型的缓存机制1.5 equals() 和 1.6 自动装箱拆箱1.7 浮点数精度丢失1.8 数值、字符转…

windows下配置pytorch + yolov8+vscode,并自定义数据进行训练、摄像头实时预测

最近由于工程需要&#xff0c;研究学习了一下windows下如何配置pytorch和yolov8&#xff0c;并自己搜集数据进行训练和预测&#xff0c;预测使用usb摄像头进行实时预测。在此记录一下全过程 一、软件安装和配置 1. vscode安装 windows平台开发python&#xff0c;我采用vscod…

Python基础合集 练习26 (turtle库的使用)

turtle是标准库 import turtle as t 窗口最小单位为像素 t.steup(width,height,起始点,起始点) 不是必须的 t.setup(800, 400) 不设置后面的起始点默认在中间 空间坐标体系 绝对坐标 四个象限 t.goto(x,y) 让某个位置的海龟到达某个地方 t.goto(100,100) t.goto(10…

使用flask开启一个简单的应用

Flask是非常流行的 Python Web框架&#xff0c;它能如此流行&#xff0c;原因主要有如下几点: 。有非常齐全的官方文档,上手非常方便。 。有非常好的扩展机制和第三方扩展环境&#xff0c;.工作中常见的软件都会有对应的扩展。自己动手实现扩展也很容易。 。社区活跃度非常高。…

【可解释学习】PyG可解释学习模块torch_geometric.explain

PyG可解释学习模块torch_geometric.explain PhiloshopyExplainerExplanationsExplainer AlgorithmExplanation Metrics参考资料 torch_geometric.explain是PyTorch Geometric库中的一个模块&#xff0c;用于解释和可视化图神经网络&#xff08;GNN&#xff09;模型的预测结果。…

RestClient操作文档和DSL查询语法

一、 文档操作 1、新增文档 本案例中&#xff0c;hotel为索引库名&#xff0c;61083为文档idTestvoid testAddDocument() throws IOException {// 1.根据id查询酒店数据Hotel hotel hotelService.getById(61083L);// 2.转换为文档类型HotelDoc hotelDoc new HotelDoc(hotel…

【数据结构】二叉树——链式结构

目录 一、前置声明 二、二叉树的遍历 2.1 前序、中序以及后序遍历 2.2 层序遍历 三、节点个数以及高度 3.1 节点个数 3.2 叶子节点个数 3.3 第k层节点个数 3.4 二叉树的高度/深度 3.5 查找值为x的节点 四、二叉树的创建和销毁 4.1 构建二叉树 4.2 二叉树销毁 4.3 …

2023年7月14日,ArrayList底层

集合框架图&#xff1a; 集合和数组的区别 AarrayList ArrayList底层实现原理 ArrayList的底层实现是基于数组的动态扩容。 初始容量&#xff1a;当创建一个新的ArrayList对象时&#xff0c;它会分配一个初始容量为10的数组。这个初始容量可以根据需求进行调整。 //表示默认的…

在Python中优雅地用多进程:进程池 Pool、管道通信 Pipe、队列通信 Queue、共享内存 Manager Value

Python 自带的多进程库 multiprocessing 可实现多进程。我想用这些短例子示范如何优雅地用多线程。中文网络上&#xff0c;有些人只是翻译了旧版的 Python 官网的多进程文档。而我这篇文章会额外讲一讲下方加粗部分的内容。 创建进程 Process&#xff0c;fork 直接继承资源&am…

zigbee DL-20无线串口收发模块使用(双车通讯,电赛模块推荐)

前言 &#xff08;1&#xff09;通常有时候&#xff0c;我们可能会需要让两个MCU进行通讯。而zigbee是最适合两个MCU短距离通讯的模块。他使用极其简单&#xff0c;非常适合两款MCU之间的进行数据交互。 &#xff08;2&#xff09;在各类比赛中&#xff0c;经常出现需要两个MCU…

独立看门狗 IWDG

独立看门狗介绍 Q&#xff1a;什么是看门狗&#xff1f; A&#xff1a;可以理解为对于一只修勾的定时投喂&#xff0c;如果不给它吃东西就会狂叫&#xff0c;因此可以通过观察修勾的状态来判断喂它的人有没有正常工作。 在由单片机构成的微型计算机系统中&#xff0c;由于单…

【业务功能篇44】Mysql 海量数据查询优化,进行分区操作

业务场景&#xff1a;当前有个发料表&#xff0c;随着业务数据量增多&#xff0c;达到了几千万级别水平&#xff0c;查询的效率就越来越低了&#xff0c;针对当前的架构情况&#xff0c;我们进行了分区的设置&#xff0c;通过对时间字段&#xff0c;按年月&#xff0c;一个月作…

ios 启动页storyboard 使用记录

本文简单记录ios启动页storyboard 如何使用和注意事项。 xcode窗口简介 以xcode14为例&#xff0c;新建项目如下图&#xff0c;左边文件栏中的LaunchScreen.storyboard 为默认启动页布局。窗口中间部分是storyboard中的组件列表&#xff0c;右侧为预览&#xff0c;可以看到渲…

H3C-Cloud Lab-实验-DHCP实验

实验拓扑图&#xff1a; 实验需求&#xff1a; 1、按照图示为R1配置IP地址 2、配置R1为DHCP服务器&#xff0c;提供服务的地址池为192.168.1.0/24网段&#xff0c;网关为192.168.1.254&#xff0c;DNS服务器地址为202.103.24.68&#xff0c;202.103.0.117 3、192.168.1.10-1…

Camtasia Studio 2023 最新中文版,camtasiaStudio如何添加背景音乐

Camtasia2023的视频编辑工具可以帮助用户剪辑、裁剪、旋转、调整大小、添加特效、混合音频等。用户还可以使用Camtasia2023的字幕功能添加字幕和注释&#xff0c;以及使用其内置的特效和转场来提高视频的视觉效果。 Camtasia Studio 2023新功能介绍 的光标增强 由于光标在屏幕…

解决win10电脑无法访问局域网内其它共享文件问题

问题描述 今天需要上传文件到一个共享的局域网文件夹里&#xff0c;在我的电脑和浏览器访问//192.168.0.16//public都提升访问受限&#xff0c;开始以为是因为用户没授权&#xff0c;后来一般沟通后&#xff0c;发现其它电脑都能正常访问的&#xff0c;所以确定是自己电脑配置…

Caerulein,17650-98-5,雨蛙肽,以三氟醋酸盐形式提供的十肽分子

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ Caerulein |雨蛙素&#xff0c;雨蛙肽&#xff0c;蓝肽| CAS&#xff1a;17650-98-5 | 纯度&#xff1a;95% ------雨蛙素结构式---- ----试剂参数信息--- CAS号&#xff1a;17650-98-5 外观&#xff08;Appearance&a…