1. 选择排序的基本原理

    • 选择排序是一种简单的排序算法。它的基本思想是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
  2. 代码实现

    • 以下是C++ 实现选择排序的示例代码:
#include <iostream>
#include <vector>
// 交换两个元素的值
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
// 选择排序函数
template <typename T>
void selectionSort(std::vector<T>& 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;
            }
        }
        if (minIndex!= i) {
            swap(arr[i], arr[minIndex]);
        }
    }
}
int main() {
    std::vector<int> numbers = {5, 4, 3, 2, 1};
    selectionSort(numbers);
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}
  • 在上述代码中:
    • 首先定义了一个swap函数,用于交换两个元素的值。这个函数是一个模板函数,可以用于交换任何类型的两个变量的值。
    • 然后定义了selectionSort函数,它也是一个模板函数,接受一个std::vector类型的参数arr。在函数内部,外层for循环从0n - 1n是数组的大小),用于确定已排序序列的末尾位置。对于每一个i,首先假设minIndex = i,即当前位置的元素是最小的。然后内层for循环从i + 1n,用于在未排序序列中寻找最小元素的下标。如果找到比arr[minIndex]更小的元素arr[j],就更新minIndex = j。最后,如果minIndex不等于i,就调用swap函数交换arr[i]arr[minIndex]的值,将找到的最小元素放到已排序序列的末尾。
    • main函数中,创建了一个std::vector类型的数组numbers,并初始化了一些值。然后调用selectionSort函数对numbers进行排序,最后遍历排序后的数组并输出结果。
  1. 时间复杂度分析

    • 最好情况:时间复杂度为O(n2)O(n^{2})。因为无论输入数据是否已经有序,选择排序都需要进行n(n1)2\frac{n(n - 1)}{2}次比较操作,其中nn是数组的长度。例如,即使数组已经是有序的,算法仍然会遍历未排序的部分来寻找最小元素。
    • 最坏情况:时间复杂度同样是O(n2)O(n^{2})。当数组是逆序排列时,每次寻找最小元素都需要遍历剩余的所有元素,比较次数达到最大值。
    • 平均情况:时间复杂度也是O(n2)O(n^{2})。因为在平均情况下,每次寻找最小元素的比较次数也大约是n2\frac{n}{2},总共需要进行n1n - 1次这样的操作,所以时间复杂度为O(n2)O(n^{2})
  2. 空间复杂度分析

    • 选择排序是一种原地排序算法,它只需要几个额外的变量(如minIndex和用于交换的临时变量)来辅助排序,这些变量所占用的空间不依赖于输入数据的规模。所以空间复杂度为O(1)O(1),这意味着它在排序过程中对额外空间的需求是一个常数。

5 条评论

  • @ 2024-12-24 20:48:01

    以下是一个满足你要求的C++选择排序代码示例,针对一维数组且下标从1开始,不使用函数模板也不使用自定义函数(除了main函数),并为每行代码添加了注释,方便你理解:

    #include <iostream>
    using namespace std;
    
    int main() {
        // 定义一个数组,为了体现下标从1开始的逻辑,数组大小多分配1个元素(第0个元素位置不使用),并初始化一些示例数据
        int arr[6] = {0, 5, 3, 8, 6, 2};  
        int size = 5; // 实际参与排序的元素个数,对应从下标1到5的元素,也就是数组中真正用于排序的部分
    
        // 外层循环控制排序的轮数,从下标1开始,一共进行size - 1轮排序,因为经过size - 1轮后,最后一个元素自然就处于正确的排序位置了
        for (int i = 1; i < size; i++) {  
            int minIndex = i; // 在每一轮排序中,先假设当前位置(下标为i)的元素是未排序部分的最小元素,记录其下标为minIndex
    
            // 内层循环用于在未排序部分(从当前位置i的下一个位置i + 1开始,到数组末尾下标为size的元素)寻找最小元素的下标
            for (int j = i + 1; j <= size; j++) {  
                // 如果发现有元素比当前认为的最小元素arr[minIndex]更小
                if (arr[j] < arr[minIndex]) {  
                    minIndex = j; // 则更新最小元素的下标为这个更小元素的下标j
                }
            }
    
            // 如果经过内层循环找到的最小元素下标minIndex和当前轮次的起始下标i不一样,说明需要交换这两个位置的元素
            if (minIndex!= i) {  
                // 使用一个临时变量temp来暂存当前位置(下标为i)的元素值,方便后续交换元素
                int temp = arr[i];  
                arr[i] = arr[minIndex]; // 将当前位置(下标为i)的元素替换为找到的最小元素(其在下标为minIndex的位置)
                arr[minIndex] = temp; // 将最小元素原来所在位置(下标为minIndex)的元素替换为之前暂存的元素(temp中存储的原arr[i]的值)
            }
        }
    
        // 循环遍历排序后的数组,从下标1开始到最后一个实际参与排序的元素(下标为size),输出数组元素
        for (int i = 1; i <= size; i++) {  
            cout << arr[i] << " ";
        }
        cout << endl;
    
        return 0;
    }
    

    时间复杂度分析

    • 最好情况:即便输入的数组在逻辑上从下标1开始已经是有序的,此算法依旧需要完整地遍历每一个元素,去确认它是否为当前未排序部分中的最小元素(尽管实际上不需要进行元素交换操作),总共需要进行的比较次数大约为 n(n1)2\frac{n(n - 1)}{2}(这里的 n 表示实际参与排序的元素个数,也就是代码中的 size),所以时间复杂度为 O(n2)O(n^2)。例如,对于数组 {0, 1, 2, 3, 4}(第0个元素不参与排序逻辑,实际排序的是从下标1开始的 1, 2, 3, 4),算法仍会按部就班地去比较每个元素位置,以确定其是否是最小元素所在位置,比较次数并不会因已有序而减少。

    • 最坏情况:当数组元素在逻辑上从下标1开始呈现逆序排列时,每一轮的外层循环中,都需要在内层循环里遍历剩余未排序元素中最多的次数,以此来找到最小元素,此时比较次数达到最大值,同样约为 n(n1)2\frac{n(n - 1)}{2},因此时间复杂度为 O(n2)O(n^2)。比如数组 {4, 3, 2, 1}(第0个元素不参与排序逻辑,实际排序的是 4, 3, 2, 1),每一轮确定最小元素都得把后面所有未排序的元素都比较一遍,随着元素个数增多,比较次数会以二次方的形式增长。

    • 平均情况:平均而言,每一轮寻找最小元素时大致需要进行一半元素数量的比较操作,整体的比较操作次数仍旧与 n 的平方成正比,所以平均时间复杂度同样是 O(n2)O(n^2)

    空间复杂度分析

    整个排序过程额外使用的空间仅有用于交换元素时的临时变量 temp 以及记录最小元素下标的 minIndex,这些变量所占用的空间大小并不会随着数组元素个数的增加而增加,是固定的常数级别,所以空间复杂度为 O(1)O(1),这表明该排序算法在排序过程中不需要额外开辟大量空间来辅助排序,属于原地排序算法。

    • @ 2024-12-24 20:47:05

      以下是一个C++实现的选择排序代码,针对一维数组且下标从1开始,不使用函数模板,同时为每行代码添加了注释,方便你理解代码逻辑:

      #include <iostream>
      using namespace std;
      
      // 选择排序函数,用于对给定的一维数组进行排序,数组下标从1开始
      void selectionSort(int arr[], int size) {
          // 外层循环控制排序的轮数,从下标1开始,一共进行size - 1轮排序,因为最后一个元素在经过size - 1轮后自然处于正确位置
          for (int i = 1; i < size; i++) {  
              int minIndex = i; // 假设当前轮次中,下标为i的元素是未排序部分的最小元素,记录其下标为minIndex
      
              // 内层循环用于在未排序部分(从当前位置i的下一个位置i + 1开始,到数组末尾下标为size的元素)寻找最小元素的下标
              for (int j = i + 1; j <= size; j++) {  
                  // 如果发现有元素比当前认为的最小元素arr[minIndex]更小
                  if (arr[j] < arr[minIndex]) {  
                      minIndex = j; // 则更新最小元素的下标为这个更小元素的下标j
                  }
              }
      
              // 如果经过内层循环找到的最小元素下标minIndex和当前轮次的起始下标i不一样,说明需要交换元素
              if (minIndex!= i) {  
                  // 定义一个临时变量temp,用于交换元素时暂存其中一个元素的值
                  int temp = arr[i];  
                  arr[i] = arr[minIndex]; // 将当前位置i的元素替换为找到的最小元素
                  arr[minIndex] = temp; // 将最小元素原来的位置(minIndex处)替换为之前暂存的元素(temp中存储的原arr[i]的值)
              }
          }
      }
      
      int main() {
          // 为了实现下标从1开始的逻辑,数组大小多分配1个元素(第0个元素不使用),并初始化一些示例数据
          int numbers[6] = {0, 5, 3, 8, 6, 2};  
          int size = 5; // 实际参与排序的元素个数,逻辑上对应从下标1到5的元素
      
          selectionSort(numbers, size); // 调用选择排序函数对numbers数组进行排序
      
          // 从下标1开始遍历数组,输出排序后的数组元素
          for (int i = 1; i <= size; i++) {  
              cout << numbers[i] << " ";
          }
          cout << endl;
      
          return 0;
      }
      

      时间复杂度分析

      • 最好情况:即使输入的数组在逻辑上从下标1开始已经是有序的,该算法仍然需要遍历数组中的每一个元素来确认它是否是当前未排序部分的最小元素(尽管不需要实际交换元素),总共需要比较的次数大约为 n(n1)2\frac{n(n - 1)}{2}(这里 n 是实际参与排序的元素个数,也就是 size),所以时间复杂度为 O(n2)O(n^2)

      • 最好情况举例:比如数组 {0, 1, 2, 3, 4}(这里第0个元素不参与排序逻辑,实际参与排序的是从下标1开始的 1, 2, 3, 4),算法还是会按部就班地去比较每一个元素位置,判断是否是最小元素所在位置,这个比较过程的次数不会因已有序而减少,依然是按照既定的比较次数进行。

      • 最坏情况:当数组元素在逻辑上从下标1开始是逆序排列时,每一轮在外层循环中,都需要在内层循环里遍历剩余未排序元素中最多的次数来找到最小元素,此时比较次数达到最大值,同样约为 n(n1)2\frac{n(n - 1)}{2},所以时间复杂度为 O(n2)O(n^2)

      • 最坏情况举例:例如数组 {4, 3, 2, 1}(同样第0个元素不参与排序逻辑,实际排序的是 4, 3, 2, 1),每一轮确定最小元素都需要把后面所有未排序的元素都比较一遍,比较次数会随着元素个数增加而以二次方的形式增长。

      • 平均情况:平均来说,每一轮寻找最小元素时大致需要进行一半元素数量的比较操作,整体的比较操作次数还是与 n 的平方成正比,所以平均时间复杂度也是 O(n2)O(n^2)

      空间复杂度分析

      整个排序过程额外使用的空间只有用于交换元素时的临时变量 temp 以及记录最小元素下标的 minIndex,这些变量所占用的空间大小不依赖于数组元素个数的多少,是固定的常数级别,所以空间复杂度为 O(1)O(1),这意味着该排序算法在排序过程中不需要额外开辟大量空间,属于原地排序算法。

      • @ 2024-12-24 20:46:13

        以下是一个C++实现的选择排序代码,其中一维数组的下标从1开始,并且为每行代码添加了注释来帮助理解:

        #include <iostream>
        using namespace std;
        
        // 交换两个元素的值,这里使用模板函数,使得可以适用于不同类型的元素交换
        template <typename T>
        void swap(T& a, T& b) {
            T temp = a;  // 创建临时变量temp,用于暂存a的值
            a = b;       // 将b的值赋给a
            b = temp;    // 将暂存的a的值(在temp中)赋给b,完成a和b值的交换
        }
        
        // 选择排序函数,实现对一维数组进行排序,数组下标从1开始的逻辑处理
        template <typename T>
        void selectionSort(T arr[], int size) {
            for (int i = 1; i < size; i++) {  // 外层循环,i从1开始,模拟下标从1开始的逻辑,每次循环确定一个位置的元素,总共循环size - 1次
                int minIndex = i;            // 先假设当前位置i的元素就是最小元素,记录其下标为minIndex
                for (int j = i + 1; j <= size; j++) {  // 内层循环,从当前位置i的下一个位置(i + 1)开始,到数组最后一个元素(下标为size),遍历未排序部分来寻找最小元素
                    if (arr[j] < arr[minIndex]) {      // 如果发现有元素比当前记录的最小元素arr[minIndex]还小
                        minIndex = j;                  // 则更新最小元素的下标为该更小元素的下标j
                    }
                }
                if (minIndex!= i) {  // 如果找到的最小元素下标minIndex和当前位置i不一样,说明需要交换元素
                    swap(arr[i], arr[minIndex]);  // 调用swap函数,交换当前位置i的元素和找到的最小元素,将最小元素放到正确位置
                }
            }
        }
        
        int main() {
            // 为了体现下标从1开始,数组多分配1个元素,第0个元素不使用,这里初始化了一些示例数据
            int numbers[6] = {0, 5, 3, 8, 6, 2};  
            int size = 5;  // 实际参与排序的元素个数,逻辑上从1到5
        
            selectionSort(numbers, size);  // 调用选择排序函数,对numbers数组进行排序
        
            for (int i = 1; i <= size; i++) {  // 从下标1开始,遍历并输出排序后的数组元素
                cout << numbers[i] << " ";
            }
            cout << endl;
        
            return 0;
        }
        

        这段代码实现了选择排序算法,使其在逻辑上对一维数组的处理从下标1开始,通过比较和交换操作,逐步将数组元素按照从小到大的顺序进行排序,并最终输出排序后的数组内容。其时间复杂度和空间复杂度的分析如下:

        时间复杂度分析

        • 最好情况:即便输入的数组在逻辑上从下标1开始已经是有序状态,该算法依旧需要遍历每一个元素去确认其是否为最小元素(尽管不需要实际交换),总共的比较次数依然约为 n(n1)2\frac{n(n - 1)}{2}(此处 n 表示实际参与排序的元素个数,即 size),所以时间复杂度为 O(n2)O(n^2)
        • 最坏情况:当数组元素在逻辑上从下标1开始呈现逆序排列时,每一轮都要在剩余未排序元素中进行最多的比较来找出最小元素,比较次数同样约为 n(n1)2\frac{n(n - 1)}{2},时间复杂度为 O(n2)O(n^2)
        • 平均情况:平均而言,每一轮寻找最小元素时大致也需要进行一半元素数量的比较操作,整体的比较操作次数仍与 n2n^2 成正比,所以平均时间复杂度同样是 O(n2)O(n^2)

        空间复杂度分析

        整个排序过程仅仅额外借助了几个临时变量(如 minIndex 以及 swap 函数里的 temp)来辅助完成排序操作,这些临时变量所占用的空间不会随数组元素个数的增多而增大,始终保持在常数级别,所以空间复杂度为 O(1)O(1),属于原地排序算法。

        • @ 2024-12-24 20:45:09

          以下是一个在C++中实现的选择排序代码,其数组下标从1开始的示例,通常在C++中数组下标默认从0开始,但按照要求可以通过一些处理让逻辑上的下标从1开始,以下是具体实现:

          #include <iostream>
          using namespace std;
          
          // 交换两个元素的值
          template <typename T>
          void swap(T& a, T& b) {
              T temp = a;
              a = b;
              b = temp;
          }
          
          // 选择排序函数,数组下标从1开始的逻辑处理
          template <typename T>
          void selectionSort(T arr[], int size) {
              for (int i = 1; i < size; i++) {
                  int minIndex = i;
                  for (int j = i + 1; j <= size; j++) {
                      if (arr[j] < arr[minIndex]) {
                          minIndex = j;
                      }
                  }
                  if (minIndex!= i) {
                      swap(arr[i], arr[minIndex]);
                  }
              }
          }
          
          int main() {
              // 为了体现下标从1开始,数组大小多分配1个元素,第0个元素不使用
              int numbers[6] = {0, 5, 3, 8, 6, 2};
              int size = 5;  // 实际参与排序的元素个数,逻辑上从1到5
          
              selectionSort(numbers, size);
          
              for (int i = 1; i <= size; i++) {
                  cout << numbers[i] << " ";
              }
              cout << endl;
          
              return 0;
          }
          

          以下是对上述代码的详细解释:

          1. swap函数

          这是一个通用的模板函数,用于交换两个同类型的变量值。它利用一个临时变量 temp 来暂存其中一个值,进而实现 ab 两个变量值的互换。在选择排序中,用于交换找到的最小元素和对应位置的元素。

          2. selectionSort函数

          • 外层循环for (int i = 1; i < size; i++),这里的 i 从1开始,模仿下标从1开始的逻辑,每一次循环就代表确定了第 i 个位置的元素(逻辑上的位置),总共会进行 size - 1 次循环,逐步确定每个位置的元素,直到整个数组排序完成(除了我们额外分配但不使用的第0个元素对应的位置)。
          • 寻找最小元素下标(内层循环)for (int j = i + 1; j <= size; j++),这个内层循环是从当前位置 i 的下一个位置(也就是 i + 1)开始,一直到数组的最后一个元素(对应下标为 size,这里 size 表示实际参与排序的最后一个元素的逻辑下标),通过比较 arr[j]arr[minIndex] 的大小来找到未排序部分中的最小元素下标,并记录在 minIndex 中。
          • 交换元素:当内层循环结束后,如果找到的最小元素下标 minIndex 不等于当前轮次对应的逻辑下标 i,那就调用 swap 函数,将当前逻辑位置 i 的元素和最小元素所在位置 minIndex 的元素进行交换,这样就把这一轮找到的最小元素放到了合适的位置,不断扩大已排序部分的范围。

          3. main函数

          • 首先定义了一个 int 类型的数组 numbers,由于要实现下标从1开始的逻辑,多分配了一个元素(第0个元素不使用),并且初始化了一些用于排序的元素值。
          • 接着定义了变量 size,它表示实际参与排序的元素个数,这里赋值为5,表示逻辑上我们使用数组中从下标1到下标5这5个元素进行排序。
          • 然后调用 selectionSort 函数对 numbers 数组(实际参与排序的部分)进行排序。
          • 最后通过一个 for 循环,从下标1开始到 size,遍历并输出排序后的数组元素,展示排序的结果。

          4. 时间复杂度分析

          • 最好情况:即使输入的数组本身已经是有序的(逻辑上从下标1开始有序),选择排序依然需要遍历数组去确认每个位置是否已经是当前最小元素(虽然不需要实际交换),总共的比较次数仍然是大约 n(n1)2\frac{n(n - 1)}{2}(这里 n 是实际参与排序的元素个数,也就是 size),所以时间复杂度为 O(n2)O(n^2)
          • 最坏情况:当数组元素是逆序排列(逻辑上从下标1开始逆序)时,每一轮都需要在剩余未排序元素中进行最多的比较来找到最小元素,比较次数同样是大约 n(n1)2\frac{n(n - 1)}{2},时间复杂度为 O(n2)O(n^2)
          • 平均情况:平均来讲,每一轮寻找最小元素也需要进行大致一半元素数量的比较操作,整体比较操作次数还是与 n2n^2 成正比,所以平均时间复杂度也是 O(n2)O(n^2)

          5. 空间复杂度分析

          整个排序过程只额外使用了几个临时变量(比如 minIndexswap 函数中的 temp)来辅助完成排序,这些临时变量所占用的空间不会随着数组元素个数的增加而增加,始终是常数级别,所以空间复杂度为 O(1)O(1),属于原地排序算法。

          • @ 2024-12-24 20:43:38

            以下是使用C++实现对一维数组进行选择排序的详细代码示例及相关解释:

            一、代码示例

            #include <iostream>
            using namespace std;
            
            // 交换两个元素的值
            template <typename T>
            void swap(T& a, T& b) {
                T temp = a;
                a = b;
                b = temp;
            }
            
            // 选择排序函数
            template <typename T>
            void selectionSort(T arr[], int size) {
                for (int i = 0; i < size - 1; i++) {
                    int minIndex = i;
                    for (int j = i + 1; j < size; j++) {
                        if (arr[j] < arr[minIndex]) {
                            minIndex = j;
                        }
                    }
                    if (minIndex!= i) {
                        swap(arr[i], arr[minIndex]);
                    }
                }
            }
            
            int main() {
                int numbers[] = {5, 3, 8, 6, 2};
                int size = sizeof(numbers) / sizeof(numbers[0]);
            
                selectionSort(numbers, size);
            
                for (int num : numbers) {
                    cout << num << " ";
                }
                cout << endl;
            
                return 0;
            }
            

            二、代码解析

            1. swap函数

              • 这是一个模板函数,用于交换两个同类型的变量的值。它接受两个引用参数ab,通过借助一个临时变量temp,实现了ab值的交换。由于使用了引用,避免了不必要的拷贝操作,提高了效率,并且可以适用于各种支持赋值操作的类型(如整数、浮点数、自定义类等)。
            2. selectionSort函数

              • 外层循环for (int i = 0; i < size - 1; i++),这个循环控制排序的轮数,总共需要进行size - 1轮,因为经过size - 1次选择和交换操作后,最后一个元素自然就处于正确的排序位置了。
              • 寻找最小元素下标(内层循环)for (int j = i + 1; j < size; j++),对于每一轮i,从当前位置i + 1开始到数组末尾,遍历未排序的部分,通过比较arr[j]arr[minIndex]的大小,找到未排序部分中的最小元素的下标,并记录在minIndex中。
              • 交换元素:在每一轮找到最小元素的下标minIndex后,如果minIndex不等于当前轮的起始下标i,就调用swap函数将当前位置i的元素和最小元素进行交换,这样就把这一轮找到的最小元素放到了已排序部分的末尾,逐渐扩大已排序部分的范围。
            3. main函数

              • 首先定义了一个整型数组numbers,并初始化了一些元素。然后通过sizeof(numbers) / sizeof(numbers[0])计算出数组的元素个数,存储在size变量中。
              • 接着调用selectionSort函数对numbers数组进行排序。
              • 最后使用基于范围的for循环遍历排序后的数组,并输出数组中的元素,展示排序的结果。

            三、时间复杂度分析

            • 最好情况:即使输入的数组本身已经是有序的,选择排序算法仍然需要遍历每一个元素,去确认它是否是最小元素(虽然实际上不需要交换),总共需要比较的次数是n(n1)2\frac{n(n - 1)}{2}nn为数组元素个数),所以时间复杂度为O(n2)O(n^2)
            • 最坏情况:当数组元素是逆序排列时,每一轮都需要在剩余未排序的元素中进行最多的比较来找到最小元素,同样比较次数是n(n1)2\frac{n(n - 1)}{2},时间复杂度为O(n2)O(n^2)
            • 平均情况:平均而言,每一轮寻找最小元素也需要进行大致一半元素数量的比较操作,整体的比较操作次数依然是与n2n^2成正比,所以平均时间复杂度也是O(n2)O(n^2)

            四、空间复杂度分析

            选择排序只需要额外使用有限个临时变量(如minIndexswap函数中的temp)来辅助完成排序过程,这些临时变量所占用的空间大小不会随着数组元素个数的增加而增加,是一个常数级别,所以空间复杂度为O(1)O(1),属于原地排序算法。

            • 1