编程算法学习——数组与排序算法

文章目录

一、数组(Array)

[1.1 数组的基本概念](#1.1 数组的基本概念)

[1.2 数组的特点](#1.2 数组的特点)

[1.3 数组的基本操作(Java示例)](#1.3 数组的基本操作(Java示例))

二、排序算法详解

[2.1 算法复杂度基础](#2.1 算法复杂度基础)

[2.2 冒泡排序(Bubble Sort)](#2.2 冒泡排序(Bubble Sort))

算法原理

算法步骤

Java实现

复杂度分析

[2.3 选择排序(Selection Sort)](#2.3 选择排序(Selection Sort))

算法原理

算法步骤

Java实现

复杂度分析

[2.4 插入排序(Insertion Sort)](#2.4 插入排序(Insertion Sort))

算法原理

算法步骤

Java实现

复杂度分析

[2.5 快速排序(Quick Sort)](#2.5 快速排序(Quick Sort))

算法原理

算法步骤

Java实现

复杂度分析

[2.6 归并排序(Merge Sort)](#2.6 归并排序(Merge Sort))

算法原理

Java实现

复杂度分析

[2.7 堆排序(Heap Sort)](#2.7 堆排序(Heap Sort))

算法原理

Java实现

复杂度分析

三、排序算法比较总结

四、实际应用与选择建议

[4.1 如何选择合适的排序算法](#4.1 如何选择合适的排序算法)

[4.2 Java中的排序](#4.2 Java中的排序)

五、实战练习

练习1:自定义比较器排序

[练习2:Top K问题](#练习2:Top K问题)


一、数组(Array)

1.1 数组的基本概念

数组是一种线性数据结构,用于存储相同类型元素的集合。在内存中,数组元素是连续存储的。

1.2 数组的特点

  • 固定大小:一旦创建,大小通常不可改变

  • 随机访问:通过索引可以在O(1)时间内访问任意元素

  • 内存连续:所有元素在内存中连续存储

  • 类型一致:所有元素必须是相同数据类型

1.3 数组的基本操作(Java示例)

java 复制代码
// 创建数组
int[] arr = new int[5]; // 创建大小为5的整型数组
int[] arr2 = {1, 2, 3, 4, 5}; // 创建并初始化

// 访问元素
int first = arr[0]; // 访问第一个元素
arr[0] = 10; // 修改第一个元素

// 获取长度
int len = arr.length;

// 遍历数组
for(int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 增强for循环
for(int num : arr) {
    System.out.println(num);
}

二、排序算法详解

2.1 算法复杂度基础

  • 时间复杂度:算法执行时间随数据规模增长的变化趋势

  • 空间复杂度:算法所需内存空间随数据规模增长的变化趋势

常见时间复杂度:

  • O(1):常数时间

  • O(log n):对数时间

  • O(n):线性时间

  • O(n log n):线性对数时间

  • O(n²):平方时间

2.2 冒泡排序(Bubble Sort)

算法原理

重复遍历数组,比较相邻元素,如果顺序错误就交换它们。

算法步骤
  1. 从第一个元素开始,比较相邻元素

  2. 如果前一个元素大于后一个元素,交换它们

  3. 对每一对相邻元素重复此操作

  4. 重复n-1轮,每轮减少一个已排序元素

Java实现
java 复制代码
public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        // 外层循环控制排序轮数
        for (int i = 0; i < n - 1; i++) {
            boolean swapped = false; // 优化:如果一轮没有交换,说明已排序完成
            
            // 内层循环进行相邻元素比较
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换元素
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true;
                }
            }
            
            // 如果没有发生交换,提前结束
            if (!swapped) {
                break;
            }
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        bubbleSort(arr);
        System.out.println("排序后数组:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:最好O(n),最坏O(n²),平均O(n²)

  • 空间复杂度:O(1)

  • 稳定性:稳定排序

2.3 选择排序(Selection Sort)

算法原理

每次从未排序部分选择最小(或最大)元素,放到已排序部分的末尾。

算法步骤
  1. 找到数组中最小的元素

  2. 将其与第一个元素交换位置

  3. 在剩余元素中找到最小元素,与第二个元素交换

  4. 重复此过程直到所有元素排序完成

Java实现
java 复制代码
public class SelectionSort {
    public static void selectionSort(int[] arr) {
        int n = arr.length;
        
        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) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {64, 25, 12, 22, 11};
        selectionSort(arr);
        System.out.println("选择排序结果:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:O(n²)(始终需要O(n²)次比较)

  • 空间复杂度:O(1)

  • 稳定性:不稳定

2.4 插入排序(Insertion Sort)

算法原理

将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入到已排序部分的正确位置。

算法步骤
  1. 从第一个元素开始,该元素可以认为已被排序

  2. 取出下一个元素,在已排序序列中从后向前扫描

  3. 如果已排序元素大于新元素,将该元素移到下一位置

  4. 重复步骤3,直到找到已排序元素小于或等于新元素的位置

  5. 将新元素插入到该位置

Java实现
java 复制代码
public class InsertionSort {
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        
        for (int i = 1; i < n; i++) {
            int key = arr[i]; // 当前要插入的元素
            int j = i - 1;
            
            // 将比key大的元素向后移动
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            
            // 插入key到正确位置
            arr[j + 1] = key;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6};
        insertionSort(arr);
        System.out.println("插入排序结果:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:最好O(n)(已排序),最坏O(n²),平均O(n²)

  • 空间复杂度:O(1)

  • 稳定性:稳定

2.5 快速排序(Quick Sort)

算法原理

采用分治策略:

  1. 选取一个基准元素(pivot)

  2. 将数组分成两部分,小于基准的放在左边,大于基准的放在右边

  3. 递归地对左右两部分进行快速排序

算法步骤
  1. 选择基准元素(通常选择最后一个元素)

  2. 初始化两个指针:i(指向小于基准的区域末尾)和j(遍历指针)

  3. 遍历数组,将小于基准的元素交换到i+1位置

  4. 最后将基准元素放到正确位置

  5. 递归排序左右两部分

Java实现
java 复制代码
public class QuickSort {
    
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 分区操作,返回基准元素的正确位置
            int pivotIndex = partition(arr, low, high);
            
            // 递归排序左半部分
            quickSort(arr, low, pivotIndex - 1);
            // 递归排序右半部分
            quickSort(arr, pivotIndex + 1, high);
        }
    }
    
    private static int partition(int[] arr, int low, int high) {
        // 选择最后一个元素作为基准
        int pivot = arr[high];
        
        // i指向小于基准的区域的最后一个元素
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            // 如果当前元素小于或等于基准
            if (arr[j] <= pivot) {
                i++;
                // 交换arr[i]和arr[j]
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        
        // 将基准元素放到正确位置
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        
        return i + 1; // 返回基准的索引
    }
    
    // 快速排序的另一种实现(更易理解)
    public static void quickSort2(int[] arr, int left, int right) {
        if (left >= right) return;
        
        int pivot = arr[(left + right) / 2]; // 选择中间元素作为基准
        int i = left, j = right;
        
        while (i <= j) {
            while (arr[i] < pivot) i++;
            while (arr[j] > pivot) j--;
            
            if (i <= j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
                i++;
                j--;
            }
        }
        
        quickSort2(arr, left, j);
        quickSort2(arr, i, right);
    }
    
    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        System.out.println("快速排序结果:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:最好O(n log n),最坏O(n²),平均O(n log n)

  • 空间复杂度:O(log n)(递归调用栈)

  • 稳定性:不稳定

2.6 归并排序(Merge Sort)

算法原理

采用分治策略:

  1. 将数组分成两半

  2. 递归地对每半进行排序

  3. 合并两个已排序的子数组

Java实现
java 复制代码
public class MergeSort {
    
    public static void mergeSort(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);
        }
    }
    
    private static void merge(int[] arr, int left, int mid, int right) {
        // 计算两个子数组的大小
        int n1 = mid - left + 1;
        int n2 = right - mid;
        
        // 创建临时数组
        int[] leftArr = new int[n1];
        int[] rightArr = new int[n2];
        
        // 拷贝数据到临时数组
        for (int i = 0; i < n1; i++) {
            leftArr[i] = arr[left + i];
        }
        for (int j = 0; j < n2; j++) {
            rightArr[j] = arr[mid + 1 + j];
        }
        
        // 合并临时数组
        int i = 0, j = 0, k = left;
        
        while (i < n1 && j < n2) {
            if (leftArr[i] <= rightArr[j]) {
                arr[k] = leftArr[i];
                i++;
            } else {
                arr[k] = rightArr[j];
                j++;
            }
            k++;
        }
        
        // 拷贝剩余元素
        while (i < n1) {
            arr[k] = leftArr[i];
            i++;
            k++;
        }
        
        while (j < n2) {
            arr[k] = rightArr[j];
            j++;
            k++;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {38, 27, 43, 3, 9, 82, 10};
        mergeSort(arr, 0, arr.length - 1);
        System.out.println("归并排序结果:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:O(n log n)(始终)

  • 空间复杂度:O(n)(需要临时数组)

  • 稳定性:稳定

2.7 堆排序(Heap Sort)

算法原理
  1. 将数组构建成最大堆

  2. 将堆顶元素(最大值)与最后一个元素交换

  3. 减少堆的大小,重新调整堆

  4. 重复步骤2-3,直到堆的大小为1

Java实现
java 复制代码
public class HeapSort {
    
    public static void heapSort(int[] arr) {
        int n = arr.length;
        
        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }
        
        // 一个个从堆顶取出元素
        for (int i = n - 1; i > 0; i--) {
            // 将当前堆顶元素(最大值)与最后一个元素交换
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            
            // 调整堆,堆大小减1
            heapify(arr, i, 0);
        }
    }
    
    // 调整堆,使以节点i为根的子树成为最大堆
    private static void heapify(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) {
            // 交换根节点和最大值节点
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;
            
            // 递归调整受影响的子树
            heapify(arr, n, largest);
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};
        heapSort(arr);
        System.out.println("堆排序结果:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
复杂度分析
  • 时间复杂度:O(n log n)

  • 空间复杂度:O(1)

  • 稳定性:不稳定

三、排序算法比较总结

排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 稳定性 适用场景
冒泡排序 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(log n) 不稳定 大规模数据,性能好
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定 需要稳定排序,外部排序
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定 内存受限,需要O(1)空间

四、实际应用与选择建议

4.1 如何选择合适的排序算法

  1. 小规模数据(n ≤ 50):插入排序或选择排序

  2. 大规模数据:快速排序、归并排序或堆排序

  3. 数据基本有序:插入排序或冒泡排序

  4. 需要稳定排序:归并排序、插入排序或冒泡排序

  5. 内存受限:堆排序(原地排序)

  6. 数据范围有限:考虑计数排序或桶排序

4.2 Java中的排序

Java的Arrays.sort()方法:

  • 对基本类型使用快速排序的变体(双轴快排)

  • 对对象类型使用归并排序(稳定)

java 复制代码
import java.util.Arrays;
import java.util.Collections;

public class JavaSortExample {
    public static void main(String[] args) {
        int[] arr = {5, 2, 9, 1, 5, 6};
        
        // 升序排序
        Arrays.sort(arr);
        
        // 降序排序(需要转换为Integer对象数组)
        Integer[] arr2 = {5, 2, 9, 1, 5, 6};
        Arrays.sort(arr2, Collections.reverseOrder());
    }
}

五、实战练习

练习1:自定义比较器排序

java 复制代码
import java.util.Arrays;
import java.util.Comparator;

class Student {
    String name;
    int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return name + ": " + score;
    }
}

public class CustomSortExample {
    public static void main(String[] args) {
        Student[] students = {
            new Student("Alice", 85),
            new Student("Bob", 92),
            new Student("Charlie", 78),
            new Student("David", 92)
        };
        
        // 按分数降序,分数相同时按名字升序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                if (s2.score != s1.score) {
                    return s2.score - s1.score; // 分数降序
                }
                return s1.name.compareTo(s2.name); // 名字升序
            }
        });
        
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

练习2:Top K问题

java 复制代码
public class TopKElements {
    // 使用快速选择算法找到第K大的元素
    public static int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, k);
    }
    
    private static int quickSelect(int[] nums, int left, int right, int k) {
        if (left == right) return nums[left];
        
        int pivotIndex = partition(nums, left, right);
        
        // 第k大的元素在排序后数组中的位置
        int targetIndex = nums.length - k;
        
        if (pivotIndex == targetIndex) {
            return nums[pivotIndex];
        } else if (pivotIndex < targetIndex) {
            return quickSelect(nums, pivotIndex + 1, right, k);
        } else {
            return quickSelect(nums, left, pivotIndex - 1, k);
        }
    }
    
    private static int partition(int[] nums, int left, int right) {
        int pivot = nums[right];
        int i = left;
        
        for (int j = left; j < right; j++) {
            if (nums[j] <= pivot) {
                swap(nums, i, j);
                i++;
            }
        }
        
        swap(nums, i, right);
        return i;
    }
    
    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    
    public static void main(String[] args) {
        int[] arr = {3, 2, 1, 5, 6, 4};
        int k = 2;
        System.out.println("第" + k + "大的元素是: " + findKthLargest(arr, k));
    }
}
相关推荐
你撅嘴真丑2 小时前
方格取数 矩阵取数游戏 -动态规划
算法·动态规划
代码游侠2 小时前
复习—sqlite基础
linux·网络·数据库·学习·sqlite
前端小L2 小时前
贪心算法专题(十三):画地为牢的艺术——「划分字母区间」
javascript·算法·贪心算法
@小码农2 小时前
202512 电子学会 Scratch图形化编程等级考试三级真题(附答案)
服务器·开发语言·数据结构·数据库·算法
坚持学习前端日记3 小时前
2025年的个人和学习年度总结以及未来期望
java·学习·程序人生·职场和发展·创业创新
橘颂TA3 小时前
【剑斩OFFER】算法的暴力美学——重排链表
算法·结构与算法
zl_vslam3 小时前
SLAM中的非线性优-3D图优化之相对位姿Between Factor位姿图优化(十三)
人工智能·算法·计算机视觉·3d
Timmylyx05183 小时前
CF 新年赛 Goodbye 2025 题解
算法·codeforces·比赛日记
闻缺陷则喜何志丹3 小时前
【二分查找】P10091 [ROIR 2022 Day 2] 分数排序|普及+
c++·算法·二分查找