一、冒泡排序
原理
冒泡排序的原理其实就是从数组的第一个元素开始向后进行两两比较,当前一位元素的值大于后一位元素的值时,那么两个元素互换位置,值大的元素放到值小的元素之后,那么第一轮排序后就可以将数组中的最大值放到最后,接着进行第二轮,同样从第一个元素开始向后进行两两比较,这次将次大值放置到数组的倒数第二个位置,以此反复,最终完成排序。值的注意的是因为第一轮确定了整个数组的最大值并将其放置到了数组的最后位置,因此第二轮的比较就不需要对最后一个元素进行比较,直接将次大值放置到倒数第二个位置,之后的比较都不与以确定位置的元素比较。冒泡排序的平均时间复杂度为O(n2),其空间复杂度为O(1)。
图解
代码示例
public static void bubbleSort(int[] data){
for (int i=0;i<data.length-1;i++) //这里的循环用以控制整体比较的次数,因为单次循环只确定一个最大值
for (int j = 0;j<data.length-1-i;j++) //这里的循环用来控制单次循环中元素向后的逐个比较
{
if (data[j]>data[j+1]) //判断当前元素是否大于下一个元素
SortTest.swap(data,j,j+1); //判断为是则交换位置,否则进行下两个元素的比较
} //swap函数是一个交换数组data中指定索引的两个元素的元素值的方法
}
二、选择排序
原理
选择排序的原理相对来说就比较简单粗暴了,它是从数组的第一个元素开始向后和所有元素进行比较,单轮比较结束后,将数组的最小值放置到数组的第一个位置,注意是所有元素进行比较,这一点从选择排序的图解中也可以看出。之后从第二个元素开始重复之前的比较,依次类推,最终完成整个数组的排序。选择排序的平均时间复杂度为O(n2),其空间复杂度为O(1)。
图解
代码示例
public static void sort(int[] arr){
for(int i = 0; i < arr.length-1; i++){ //这层循环控制整体循环次数
int min = i; //记录此轮最小值放置的位置
for(int j = i+1; j <arr.length ;j++){ //从此轮最小值位置向后比较
if(arr[j]<arr[min])
min = j; //当此轮中最小值的索引用min记录
}
if(min!=i) //当最小值的索引不同与当前位置时即当前位置的元素不为最小值
SortTest.swap(arr, i, min); //将最小值的索引位置的元素与当前索引元素进行交换
} //swap函数是一个交换数组data中指定索引的两个元素的元素值的方法
}
三、插入排序
原理
插入排序就是将需要进行排序的数组分成有序和无序两个部分,每轮排序即将无序列表中的第一个元素(无序列表中的第一个元素往往是这个数组的第二个元素,因为需要向前比较嘛(●’◡’●))向前与其它元素比较,找到插入的位置进行存放,形成一个有序列表,当排序结束后,无序列表中的元素就全部插入到了有序列表中,如下图所示。插入排序的平均时间复杂度为O(n2),其空间复杂度为O(1)。
图解
代码示例
public static void sort(int[] data){
for (int i=1;i<data.length;i++) //i控制无序列表中当前需要排序的元素
for (int j=i;(j>0&&data[j-1]>data[j]);j--)//此循环通过j来进行i元素与前面有序列表中的元素的比较
{
SortTest.swap(data,j,j-1); //当满足for循环中表达式二即索引为i的元素小于其之前的元素时,交换位置进行排序
} //swap函数是一个交换数组data中指定索引的两个元素的元素值的方法
}
四、希尔排序
原理
希尔排序其实就是借助插入排序,通过一种特殊的运用,完成对数组的排序,这种方式是什么呢?其实就是取特定的增量对数组进行分组,例如{1,2,6,4,9,7}这样一个整型数组,我们取增量为3,那么1与4,2与9,6与7即构成三组数据,然后对这三组数据内部进行一次插排,得到一个新的数组,然后我们缩小增量再次进行相同的操作,直到增量为1即所有元素构成的一个数组,此时我们进行最后一次插排完成对数组的排序。因为从第二次插排开始,数组已经有一定的顺序结构了,因此排序的效率就会比没有顺序结构进行排序高。大家可以看看图解进行理解。希尔排序的平均时间复杂度为O,其空间复杂度为O(1)。
图解
图中第一躺开始其数组顺序不同于之前是因为在之前的分组中就已经进行了排序,组内值小的元素放前,大的放后。
代码示例
public static void sort(int[] data){
for (int i =data.length/2;i>2;i/=2){ //这层循环是控制增量,一般增量取数组长度的二分之一,之后在此增量上折半
for (int j=i;j< data.length;j++) //按照增量进行分组
ShellSort.insertSort(data,j,i); //组内进行排序
}
ShellSort.insertSort(data,0,1); //当增量为1时即所有元素都在一组,所以直接进行插排
}
不懂插入排序的可以在本文前面部分查找,学习
五、快速排序
原理
快速排序的原理就是取一个基准值,然后在整个数组中去找比它小的元素,将其放置在基准值的左边,找到比它大的元素,将其放置在基准值的右边。当第一次排序完成后再从基准值的右边部分和左边部分再使用相同的方法进行排序即选取基准值,基准值左边放小于它的元素,右边放大于它的元素,那么怎样判断一次排序完成,又怎么去找这些元素呢?这里就需要设置两个变量left和right(变量名无所谓,重要的使用),我们将left放置在数组的最左边,right放置在数组的最右边,通过left从左边找比基准值大的元素,通过right从右边找比基准值小的元素,找到后将left和right所指的元素进行互换,当left++,right–后left==right时,即表示排序完成。快速排序的平均时间复杂度为 O(n*log2n),其空间复杂度为 O(log2n)~O(n)。
图解
代码示例
private static void quickSort(int[] data, int i, int j) {
int pivotIndex = (i + j) / 2; //找中心索引
SortTest.swap(data, pivotIndex, j); //将基准值放置到数组的最后
int k = partition(data, i - 1, j, data[j]); //进行排序,并返回排序后的中心索引
SortTest.swap(data, k, j); //将基准值放到其中心位置
if ((k - i) > 1) //当中心索引到数组最左边中间还有元素
quickSort(data, i, k - 1); //再次进行快排
if ((j - k) > 1) //当中心索引到数组最右边中间还有元素
quickSort(data, k + 1, j); //再次进行快排
}
private static int partition(int[] data, int l, int r, int pivot) {
do {
while (data[++l] < pivot) //当左边数据小于基准值时,满足需求,不做处理
; //(需求:左边元素小于基准值,右边大于)
while ((r != 0) && data[--r] > pivot) //当右边数据大于基准值时,满足需求,不做处理
;
SortTest.swap(data, l, r); //不满足上述情况即不满足需求,需要进行元素的交换。
} while (l < r);
SortTest.swap(data, l, r); //上述do-while循环会多进行一次交换,这里交换回来
return l; //返回l用于确认是否还有进行两边的排序
}
六、归并排序
原理
归并排序,顾名思义。其原理就是对数组的分离与合并。首先将数组逐步分离至单个元素为一组的的子序列,接着将子序列两两合并,在合并的过程中对数组进行排序,最后的两个数组进行比较后形成最终的排序结果。归并排序是分治法的体现,将问题分解成若干小部分在分别进行处理即分而治之。在下方的静态图中我们也可以看到,当初始数组分成单个元素为一组的元素后开始解决,子序列两两合并,在合并的时候两个数组的元素进行比较,值小的元素就先放入目标数组,进而形成排序好的新数组。归并排序的平均时间复杂度为 O(n*logn),其空间复杂度为 O(n)。
图解
代码示例
public static void sort(int[] data) {
int[] temp = new int[data.length]; //定义一个临时数组用于存放元素
mergeSort(data, temp, 0, data.length - 1);
}
private static void mergeSort(int[] data, int[] temp, int l, int r) {
int mid = (l + r) / 2;
if (l == r)
return;
mergeSort(data, temp, l, mid); //拆分左边
mergeSort(data, temp, mid + 1, r); //拆分右边
for (int i = l; i <= r; i++) { //将数组中的元素赋值给临时数组进行排序
temp[i] = data[i];
}
int i1 = l;
int i2 = mid + 1;
for (int cur = l; cur <= r; cur++) {
if (i1 == mid + 1) //当左边遍历完后,将右边剩余的元素放入数组
data[cur] = temp[i2++];
else if (i2 > r) //当右边遍历完后,将右边剩余的元素放入数组
data[cur] = temp[i1++];
else if (temp[i1] < temp[i2]) //左边的小,将左边的元素先放入
data[cur] = temp[i1++];
else //右边的小就先将右边的元素放入
data[cur] = temp[i2++];
}
}
七、堆排序
原理
堆排序如下图,可以很明显的看出堆排序的原理是依赖于二叉树实现的,这里首先将待排序的数组构造成一个堆如果需要将数组由小到大进行排序就构建大根堆,如果需要数组由大到小排序就构造小根堆即可,这里构造大根堆,即根节点的值大于其左孩子和右孩子节点,此时堆定的元素为整个数组的最大值,将最大值元素与末尾的元素进行交换,取出末尾的元素放入有序数组,将剩下的元素重新构建大根堆,重复进行之前的操作,最终将所有元素构造成从小到大的有序数组。堆排序的平均时间复杂度为 O(n*log2n),其空间复杂度为 O(1)。
图解
代码示例
public static void sort(int[] data) {
MaxHeap h = new MaxHeap();
h.init(data);
for (int i = 0; i < data.length; i++)
h.remove();
System.arraycopy(h.queue, 1, data, 0, data.length);
}
private static class MaxHeap {
void init(int[] data) {
this.queue = new int[data.length + 1];
for (int i = 0; i < data.length; i++) {
queue[++size] = data[i];
fixUp(size);
}
}
private int size = 0;
private int[] queue;
public int get() {
return queue[1];
}
public void remove() {
SortTest.swap(queue, 1, size--);
fixDown(1);
}
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size) {
if (j < size && queue[j] < queue[j + 1])
j++;
if (queue[k] > queue[j]) // 不用交换
break;
SortTest.swap(queue, j, k);
k = j;
}
}
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j] > queue[k])
break;
SortTest.swap(queue, j, k);
k = j;
}
}
}
简单的事情重复做,重复的事情坚持做!!