排序算法总结(含链表)

排序算法

1. 比较排序算法

这些排序算法基于元素之间的比较进行排序,最常见的几种有:

1.1. 冒泡排序(Bubble Sort)

  • 工作原理:相邻的元素两两比较,较大的元素冒泡到最后,重复这一过程,直到整个数组有序。
  • 时间复杂度:最优 O(n),最差 O(n²),平均 O(n²)。
  • 特点:简单但效率较低,适合小规模数据。

1.2. 选择排序(Selection Sort)

  • 工作原理:每次从未排序的部分中选择最小(或最大)的元素,将其放到已排序部分的末尾,重复这一过程。
  • 时间复杂度:最优 O(n²),最差 O(n²),平均 O(n²)。
  • 特点:相比冒泡排序,减少了交换次数,但仍然效率不高。

1.3. 插入排序(Insertion Sort)

  • 工作原理:每次将一个未排序的元素插入到已排序的部分中,直到整个数组有序。
  • 时间复杂度:最优 O(n),最差 O(n²),平均 O(n²)。
  • 特点:在数据基本有序时表现优异,适合小规模数据。

1.4. 希尔排序(Shell Sort)

  • 工作原理:基于插入排序的改进,通过比较相隔一定间隔的元素(逐步缩小间隔)来进行多次插入排序。
  • 时间复杂度:平均 O(n log n) 到 O(n²),最差 O(n²)。
  • 特点:改进了插入排序的效率,适合中等规模的数据。

1.5. 归并排序(Merge Sort)

  • 工作原理:采用分治法,将数组递归地分成两部分,分别排序后合并。
  • 时间复杂度:最优、最差和平均都是 O(n log n)。
  • 特点:稳定排序,适合大规模数据排序,但需要 O(n) 的额外空间。

1.6. 快速排序(Quick Sort)

  • 工作原理:选择一个基准元素,将数组分成两部分,一部分小于基准,一部分大于基准,递归地对两部分进行排序。
  • 时间复杂度:最优 O(n log n),最差 O(n²),平均 O(n log n)。
  • 特点:效率高,尤其在数据量大时,最常用的排序算法之一,但不稳定。

1.7. 堆排序(Heap Sort)

  • 工作原理:将数组看作完全二叉树,调整为最大(最小)堆,然后依次取出堆顶元素。
  • 时间复杂度:最优、最差和平均都是 O(n log n)。
  • 特点:不需要额外空间,适合需要较高空间效率的场景。

2. 非比较排序算法

这些排序算法并不依赖元素之间的比较来确定顺序,因此可以突破 O(n log n) 的时间复杂度。

2.1. 计数排序(Counting Sort)

  • 工作原理:适用于已知范围的整数,计算每个数出现的次数,然后按次数重构数组。
  • 时间复杂度 :最优、最差和平均都是 O(n + k),其中 k 是数值范围。
  • 特点:非常快,但受限于数据的取值范围,适合整数排序。

2.2. 基数排序(Radix Sort)

  • 工作原理:将数按位数进行排序(从低位到高位或反过来),多次排序使用稳定的排序算法(如计数排序或桶排序)。
  • 时间复杂度 :最优、最差和平均都是 O(n * d),其中 d 是数字的位数。
  • 特点:适合对整数或字符串等有多位数值的数据进行排序。

2.3. 桶排序(Bucket Sort)

  • 工作原理:将数组分成多个桶,每个桶内分别排序(通常使用插入排序或其他排序算法),然后合并。
  • 时间复杂度:最优 O(n),最差 O(n²),平均 O(n + k)。
  • 特点:适合数据比较均匀分布的情况,适用于浮点数排序。

3. 排序算法总结

排序算法 时间复杂度(平均) 时间复杂度(最优) 时间复杂度(最差) 空间复杂度 稳定性
冒泡排序 O(n²) O(n) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n) O(n²) O(1) 稳定
希尔排序 O(n log n) O(n log² n) O(n²) O(1) 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定
快速排序 O(n log n) O(n log n) O(n²) O(log n) 不稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定
计数排序 O(n + k) O(n + k) O(n + k) O(k) 稳定
桶排序 O(n + k) O(n) O(n²) O(n + k) 稳定
基数排序 O(n * d) O(n * d) O(n * d) O(n + k) 稳定

4. 选择排序算法的依据

不同的排序算法在不同的场景下有各自的优缺点,选择时可以考虑以下因素:

  • 数据规模:数据量大时,时间复杂度低的算法(如快速排序、归并排序、堆排序)更为适合。
  • 数据特点:对于整数、小范围数据,计数排序和基数排序有很好的表现;数据接近有序时,插入排序的表现优异。
  • 稳定性需求:如果排序后需要保持相同值的元素的相对顺序,必须选择稳定的排序算法,如归并排序、插入排序等。

1. 冒泡排序(Bubble Sort)

cpp 复制代码
void bubbleSort(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; i++) {
        bool swapped = false;
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        // 如果没有交换,说明数组已排序
        if (!swapped) break;
    }
}

2. 选择排序(Selection Sort)

cpp 复制代码
void selectionSort(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        std::swap(arr[i], arr[minIndex]);
    }
}

3. 插入排序(Insertion Sort)

  • 通用
cpp 复制代码
void insertionSort(std::vector<int>& arr) {
    int n = arr.size();
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        // 将元素逐个比较并插入到合适位置
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}
  • 链表

链表中的插入排序实现步骤

  1. 创建一个新的已排序链表。
  2. 逐个遍历原链表,将每个节点插入到新链表的正确位置。
  3. 保持新链表始终是有序的。

插入排序的时间复杂度为 O(n²),适合小规模链表或基本有序的链表。

cpp 复制代码

4. 希尔排序(Shell Sort)

cpp 复制代码
void shellSort(std::vector<int>& arr) {
    int n = arr.size();
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

5. 归并排序(Merge Sort)

  • 通用
cpp 复制代码
void merge(std::vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    std::vector<int> L(n1), R(n2);

    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }

    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }

    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}
  • 链表中的归并排序

链表中的归并排序实现步骤

涉及到链表元素变动的一般都有哨兵节点;

  1. 使用快慢指针(slowfast)找到链表的中间节点,将链表分成两半。
  2. 递归地对两部分进行排序。
  3. 合并两个已排序的链表。

注意:

查找中心点的while循环,得根据fast的初始值来进行设计,如果fast=head,则可以用fast→next&&fast→next→next

cpp 复制代码
struct ListNode{
	int val;
	ListNode* next;
	ListNode(int x): val(x),next(nullptr){}
};
class Solution{
	public:
			ListNode* mergeSort(ListNode* head){
					if(!head || !head->next) return head;
					ListNode* slow = head;
					ListNode* fast = head->next;
					//找中心点
					while(fast&&fast->next){
						slow = slow->next;
						fast = fast->next->next;
					}
					// 分左右
					ListNode* mid = slow->next;
					//断开左右
					slow->next = nullptr;
					//递归左右,得到左右子链表
					ListNode* rightHead = mergeSort(mid);
					ListNode* leftHead = mergeSort(head);
					//合并两个排序的子链表
					return merge(leftHead,rightHead);			
			 }
		private:
				ListNode* merge(ListNode* l,ListNode* r){
					ListNode dummy(0);
					ListNode* cur = &dummy;			
					while(l&&r){
						if(l->val<r-val){
							cur->next = l;
							l = l->next;
						}else{
							cur->next = r;
							r = r->next;
						}
						cur = cur->next;
					}
					cur->next = l1?l1:l2;
					return dummy.next;
				}
}

6. 快速排序(Quick Sort)

  • 通用
cpp 复制代码
int partition(std::vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
  • 链表

链表中的快速排序实现步骤

  1. 选择一个基准元素(通常选择链表的头节点)。
  2. 将链表分为三个部分:小于基准的元素,等于基准的元素,大于基准的元素。
  3. 递归地对"小于基准"的部分和"大于基准"的部分进行排序。
  4. 最后将三部分合并。

注意:

递归的开头一定是判断,如果递归的是快慢那么一定是!head||!head→next

cpp 复制代码
class Solution {
public:
    ListNode* quickSort(ListNode* head) {
        if (!head || !head->next) return head;

        // Partition the list into three parts
        ListNode* pivot = head;
        ListNode* lessHead = new ListNode(0), *greaterHead = new ListNode(0);
        ListNode* less = lessHead, *greater = greaterHead, *equal = pivot;
        ListNode* curr = head->next;

        while (curr) {
            if (curr->val < pivot->val) {
                less->next = curr;
                less = less->next;
            } else if (curr->val > pivot->val) {
                greater->next = curr;
                greater = greater->next;
            } else {
                equal->next = curr;
                equal = equal->next;
            }
            curr = curr->next;
        }
				//断开链表
        less->next = nullptr;
        greater->next = nullptr;
        equal->next = nullptr;

        // Recursively sort the less and greater partitions
        ListNode* sortedLess = quickSort(lessHead->next);
        ListNode* sortedGreater = quickSort(greaterHead->next);

        // Combine all parts together
        return combine(sortedLess, pivot, sortedGreater);
    }

private:
    ListNode* combine(ListNode* less, ListNode* pivot, ListNode* greater) {
        ListNode dummy(0), *tail = &dummy;

        if (less) {
            tail->next = less;
            //将tail排到尾巴
            while (tail->next) tail = tail->next;
        }

        tail->next = pivot;
        //将tail放到尾巴
        while (tail->next) tail = tail->next;

        tail->next = greater;
        return dummy.next;
    }
};

7. 堆排序(Heap Sort)

cpp 复制代码
void heapify(std::vector<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) {
        std::swap(arr[i], arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(std::vector<int>& arr) {
    int n = arr.size();

    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 依次从堆中取出元素
    for (int i = n - 1; i > 0; i--) {
        std::swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}

8. 计数排序(Counting Sort)

cpp 复制代码
void countingSort(std::vector<int>& arr) {
    if (arr.empty()) return;

    int maxVal = *max_element(arr.begin(), arr.end());
    int minVal = *min_element(arr.begin(), arr.end());
    int range = maxVal - minVal + 1;

    std::vector<int> count(range, 0), output(arr.size());

    // 计数
    for (int i = 0; i < arr.size(); i++)
        count[arr[i] - minVal]++;

    // 累积计数
    for (int i = 1; i < count.size(); i++)
        count[i] += count[i - 1];

    // 构建输出数组
    for (int i = arr.size() - 1; i >= 0; i--) {
        output[count[arr[i] - minVal] - 1] = arr[i];
        count[arr[i] - minVal]--;
    }

    // 将排序后的结果复制回原数组
    for (int i = 0; i < arr.size(); i++)
        arr[i] = output[i];
}

9. 基数排序(Radix Sort)

cpp 复制代码
void countingSortForRadix(std::vector<int>& arr, int exp) {
    int n = arr.size();
    std::vector<int> output(n), count(10, 0);

    // 计数
    for (int i = 0; i < n; i++)
        count[(arr[i] / exp) % 10]++;

    // 累积计数
    for (int i = 1; i < 10; i++)
        count[i] += count[i - 1];

    // 构建输出数组
    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }

    // 复制回原数组
    for (int i = 0; i < n; i++)
        arr[i] = output[i];
}

void radixSort(std::vector<int>& arr) {
    int maxVal = *max_element(arr.begin(), arr.end());

    // 基数排序
    for (int exp = 1; maxVal / exp > 0; exp *= 10)
        countingSortForRadix(arr, exp);
}

10. 桶排序(Bucket Sort)

cpp 复制代码
void bucketSort(std::vector<float>& arr) {
    int n = arr.size();
    std::vector<std::vector<float>> buckets(n);

    // 将元素分配到桶
    for (int i = 0; i < n; i++) {
        int bucketIndex = n * arr[i];
        buckets[bucketIndex].push_back(arr[i]);
    }

    // 对每个桶内的元素进行排序
    for (int i = 0; i < n; i++) {
        std::sort(buckets[i].begin(), buckets[i].end());
    }

    // 合并所有桶中的元素
    int index = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < buckets[i].size(); j++) {
            arr[index++] = buckets[i][j];
        }
    }
}
相关推荐
寂静山林6 分钟前
UVa 11855 Buzzwords
算法
Curry_Math10 分钟前
LeetCode 热题100之技巧关卡
算法·leetcode
ahadee18 分钟前
蓝桥杯每日真题 - 第10天
c语言·vscode·算法·蓝桥杯
军训猫猫头1 小时前
35.矩阵格式的一到一百数字 C语言
c语言·算法
Mr_Xuhhh2 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
SoraLuna2 小时前
「Mac玩转仓颉内测版12」PTA刷题篇3 - L1-003 个位数统计
算法·macos·cangjie
爱吃生蚝的于勒4 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
ChoSeitaku9 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___9 小时前
不使用递归的决策树生成算法
算法
我爱工作&工作love我9 小时前
1435:【例题3】曲线 一本通 代替三分
c++·算法