数据结构笔记——Java 手写快速排序 + 简易 ArrayList 底层实现

一、快速排序 QuickSort

1. 算法思想(分治思想)

  1. 选基准值:以数组最左侧元素作为基准 base
  2. 左右双指针遍历:右指针先往左找小于基准的数,左指针往右找大于基准的数,两数交换
  3. 指针相遇时,基准归位:此时基准左边全部≤基准,右边全部≥基准
  4. 递归处理基准左侧、右侧子数组,直到区间只有一个元素(递归终止)

2. 完整源码 QuickSort.java

复制代码
package com.qcby.sort;
import java.util.Arrays;

public class QuickSort {
	public static void main(String[] args) {
		int[] arr = {5,7,4,2,0,3,1,6};
		sort(arr,0,arr.length-1);
		System.out.println(Arrays.toString(arr));
	}
	
	/**
	 * 快速排序递归方法
	 * @param arr 待排序数组
	 * @param left 左边界下标
	 * @param right 右边界下标
	 */
	public static void sort(int[] arr,int left,int right) {
		// 递归终止:左边界>=右边界,区间无元素/单个元素无需排序
		if(left>=right) {
			return;
		}
		// 选取最左侧作为基准值
		int base = arr[left];
		int i=left;  // 左指针
		int j=right; // 右指针
		
		// 左右指针未相遇时循环遍历
		while(i!=j) {
			// 右指针向左寻找小于base的元素
			while(arr[j]>=base&&i!=j) {
				j--;
			}
			// 左指针向右寻找大于base的元素
			while(arr[i]<=base&&i!=j) {
				i++;
			}
			// 交换i、j指针位置元素
			int temp = arr[i];
			arr[i]=arr[j];
			arr[j]=temp;
		}
		// 基准值归位:把相遇位置元素放到原基准位,基准放入中间分割点
		arr[left]=arr[i];
		arr[i]=base;
		
		// 递归排序左区间 [left, i-1]
		sort(arr,left,i-1);
		// 递归排序右区间 [i+1, right]
		sort(arr,i+1,right);
	}
}

3. 运行结果

输入数组:{5,7,4,2,0,3,1,6} 输出:[0, 1, 2, 3, 4, 5, 6, 7]

4. 优缺点总结

  • 优点:平均时间复杂度 O (nlogn),实际排序效率极高,是工业最常用排序
  • 缺点:最坏 O (n²)(有序数组选首元素做基准);递归深度大时会栈溢出

二、手动模拟 ArrayList 底层实现

需求说明

基于原生数组手动实现简易 ArrayList,实现三大核心功能:

  1. add () 添加元素,数组满时自动 1.5 倍扩容
  2. delete () 删除数组中所有匹配值的元素
  3. toString () 自定义格式化打印集合

1. 自定义集合 ArrayList.java

复制代码
package com.qcby.array;

public class ArrayList {
	// 底层存储数组,初始容量10
	int[] arr = new int[10];
	// 实际存储元素个数
	int size = 0;
	
	/**
	 * 添加元素,自动扩容
	 * @param value 待添加数字
	 */
	public void add(int value) {
		// 判断数组是否存满
		if(size==arr.length) {
			// 扩容为原长度1.5倍
			int[] brr = new int[(int) (arr.length*1.5)];
			// 拷贝旧数组数据到新数组
			for(int i=0;i<arr.length;i++) {
				brr[i]=arr[i];
			}
			// 底层数组指向扩容后的新数组
			arr=brr;
		}
		// 存入元素
		arr[size]=value;
		// 有效元素数量+1
		size++;
	}
	
	/**
	 * 删除数组中所有等于value的元素
	 * @param value 需要删除的目标值
	 */
	public void delete(int value) {
		// 倒序遍历,防止删除后元素前移导致漏删
		for(int i = size-1;i>=0;i--) {
			if(arr[i]==value) {
				// 当前元素后所有元素向前覆盖一位
				for(int j=i+1;j<size;j++) {
					arr[j-1]=arr[j];
				}
				// 有效长度-1
				size--;
			}
		}
	}
	
	/**
	 * 重写toString,格式化输出集合
	 */
	public String toString() {
       String str = "[";
       for(int i=0;i<size;i++) {
    	   if(i==size-1) {
    		   str = str+arr[i];
    	   }else {
    		   str = str+arr[i] + ", ";
    	   }
       }
       str = str+"]";
       return str;
    }
}

2. 测试类 Test.java

复制代码
package com.qcby.array;

public class Test {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		// 添加大量测试数据,触发自动扩容
		list.add(10);
		list.add(5);
		list.add(14);
		list.add(1);
		list.add(26);
		list.add(7);
		list.add(26);
		list.add(19);
		list.add(31);
		list.add(26);
		list.add(26);
		list.add(179);
		list.add(20);
		list.add(26);
		list.add(50);
		list.add(80);
		list.add(90);
		
		System.out.println("原始集合:"+list.toString());
		list.delete(1);
		System.out.println("删除1后:"+list.toString());
		list.delete(26);
		System.out.println("删除全部26后:"+list.toString());
	}
}

3. 核心知识点解析

(1)自动扩容机制
  1. 底层数组初始容量:10
  2. size == arr.length代表数组已满
  3. 新建长度为原数组 1.5 倍的数组,循环拷贝旧数组元素,替换底层数组
(2)删除设计:倒序遍历
  • 正序遍历删除元素时,数组元素前移会跳过下一个待判断元素,出现漏删
  • 倒序从尾部向前遍历,删除元素不会影响前面未遍历下标,可删除所有匹配值
(3)自定义 toString

拼接字符串输出标准集合格式 [10, 5, 14...],区分底层数组空余位置,只打印有效 size 范围内元素

三、总结

  1. 快速排序
    • 不要忘记基准归位操作,否则数组排序完全错乱
    • 递归终止条件left>=right不能省略,否则无限递归栈溢出
  2. 自定义 ArrayList
    • 扩容必须替换底层数组引用arr=brr,否则扩容失效
    • 删除必须倒序遍历,否则重复元素无法全部删除
    • size 代表有效元素个数,打印、增删全部依赖 size,不能使用数组 length

四、拓展优化方向

  1. 快速排序:优化基准(随机基准 / 三数取中法)、小数组改用插入排序、非递归实现避免栈溢出
  2. 自定义 ArrayList:使用泛型支持任意引用类型、按下标删除、get/set 方法、缩容机制