Java Algorithm Foundation - Sorting Algorithm

First, the sorting algorithm

The test data for the following code is:

int[] arr = new int[] {10, 5, 3, 8, 2, 4, 9, 1, 7, 6};

1. Bubble Sort

The core idea is: for a set of numbers to be sorted, compare the numbers in the first position with the numbers after them one by one, and swap them if they are in the wrong order. The name of this algorithm comes from the fact that larger elements are slowly "floated" to the top of the sequence by swapping.

public static void bubbleSort1(int[] arr) {
    int count1 = 0;//记录循环次数
    int count2 = 0;//记录交换次数
    for (int i = 0; i < arr.length - 1; i++) {
        count1++;
        for (int j = 0; j < arr.length - 1 - i; j++) {
            count1++;
            if (arr[j] > arr[j + 1]) {
                count2++;
                swap(arr, j, j + 1);
            }               
        }           
    }
    System.out.println("bubbleSort1:循环次数" + count1 + ",交换次数:" + count2);
}
public static void bubbleSort2(int[] arr) {
    int count1 = 0;//记录循环次数
    int count2 = 0;//记录交换次数
    for (int i = 0; i < arr.length - 1; i++) {
        count1++;
        for (int j = i; j < arr.length - 1; j++) {
            count1++;
            if (arr[i] > arr[j + 1]) {
                count2++;
                swap(arr, i, j + 1);                  
            }
        }           
    }
    System.out.println("bubbleSort2:循环次数" + count1 + ",交换次数:" + count2);
}
private static void swap(int arr[],int i,int j) {
    int temp =arr[i];
    arr[i] = arr[j];
    arr[j] = temp; 
}

The output is:
bubbleSort: 54 cycles, 26 exchanges.

Algorithm time complexity analysis:

For the analysis of the time complexity of the sorting algorithm, two aspects should be considered, one is the number of comparisons and the other is the number of exchanges. For an array of n elements, n-1 sorting is required. Each sorting needs to perform ni keyword comparisons (1≤i≤n-1).

The time complexity of the number of comparisons:

The first pass: n-1 comparisons, the second pass: n-2 comparisons, ..., the n-1th pass, 1 comparison. So figure out:
write picture description here

The time complexity of the number of exchanges:

During the comparison process, the swap is not necessary, only if the order is not correct, it will be swapped. If the array is in ascending order, but we want to sort in descending order, then each comparison needs to be swapped, at this time the maximum number of swaps is reached, and each comparison must move the record three times to reach the swap record position. So figure out:
write picture description here

To sum up, the total average time complexity of bubble sort is O(N squared).

Algorithm stability analysis:

Bubble sort is to move small elements forward or large elements backward. Two adjacent elements are compared, and the swap also occurs between these two elements. So, if two elements are equal, I think you will not be boring to swap them; if two equal elements are not adjacent, then even if the two elements are adjacent by the previous pairwise exchange, At this time, it will not be exchanged, so the order of the same elements does not change, so bubble sort is a stable sorting algorithm.

2. Selection Sort

Selection sort is a simple and intuitive sorting algorithm. Its working principle is to select the smallest (or largest) element from the data elements to be sorted each time, and store it at the beginning of the sequence until all the data elements to be sorted are exhausted.

public static void selectSort(int[] arr) {
    int count1 = 0;//记录循环次数
    int count2 = 0;//记录交换次数
    for (int i = 0; i < arr.length - 1; i++) {
        int index = i;
        count1++;
        for (int j = i + 1; j < arr.length; j++) {
            count1++;
            if (arr[index] > arr[j]) {
                index = j;
            }
        }
        if (index != i) {
            count2++;
            swap(arr, index, i);
        }
    }
    System.out.println("selectSort:循环次数" + count1 + ",交换次数:" + count2);
}
private static void swap(int arr[],int i,int j) {
    int temp =arr[i];
    arr[i] = arr[j];
    arr[j] = temp; 
}

The output is:
bubbleSort: 54 cycles, 6 exchanges.

Algorithm time complexity analysis:

Selection sort is only a little more optimized than bubble sort, the number of comparisons has not changed, but the number of swaps has been reduced.

Algorithm stability analysis:

Selection sort is an unstable sorting method (for example, the sequence [5, 5, 3] swaps the first [5] with [3] for the first time, causing the first 5 to move behind the second 5), so The order before and after the same element is changed.

3. Insertion Sort

Insert Sort divides the array to be sorted into two parts: an ordered area and an unordered area. The core idea is to take out the first element in the disordered area each time and insert it into the ordered area. The division of ordered and unordered areas is to use a variable to mark the current array, how many elements are already locally ordered.

Insertion sort is further divided into: direct insertion sort, binary insertion sort (also known as half-insertion sort), linked list insertion sort, and Hill sort (also known as shrinking incremental sort).

Algorithm time complexity analysis:

Pass 1: Comparing at most 1 time, Pass 2: Comparing at most 2 times, ..., Pass n-1: Comparing at most n-1 times. So you need to compare at most n*(n-1)/2 times.

Bubble sort also needs to compare n*(n-1)/2 times, but the difference between the two is that bubble sort definitely needs to compare n*(n-1)/2 times, and insertion sort only works in the worst case In this case, n*(n-1)/2 times are needed. Looking back at the break in the above example, when we find that an element does not match, we will jump out directly, and the elements before the ordered area will not be compared again. Now, from a probability point of view, only half of the elements in the sorted area need to be compared, so it needs to be divided by 2, that is, the average time complexity of insertion sort comparison is n*(n-1)/4, So sometimes we see that insertion sort is twice as efficient as bubble sort.

Algorithm stability analysis:

It belongs to a kind of stable sorting (in layman's terms, two equal numbers do not exchange positions).

1) Direct insertion sort

public static void straightInsertionSort(int[] arr){
    int count1 = 0;//记录循环次数
    int count2 = 0;//记录交换次数
    for (int i = 1; i < arr.length; i++) {//从无序区第一个元素开始迭代
        count1++;
        int temp = arr[i];//记录无序区第一个元素的值
        int insertIndex = i;//记录在有序区中插入索引的位置,刚开始就设置为自己的位置
        for (int j = i - 1; j >= 0; j--) {//从有序区最后一个元素开始比较
            count1++;
            if (temp < arr[j]) {
                count2++;
                arr[j + 1] = arr[j];
                insertIndex--;//有序区每移动一次,将插入位置-1
            }else {
                break;//有序区当前位置元素<=无序区第一个元素,那么之前的元素都会<=,不需要继续比较
            }
        }
        arr[insertIndex] = temp;
    }
    System.out.println("straightInsertionSort:循环次数" + count1 + ",交换次数:"
                + count2);
}

The output is:
straightInsertionSort: 40 cycles, 26 exchanges.

2) Binary insertion sort

The difference between binary insertion sort and direct insertion sort is that direct insertion sort is to compare each data item in the iterative ordered area with the first element in the unordered area. The binary insertion sort actually makes full use of the ordered area. is specific, we know that for an ordered array, we can use binary search to quickly locate the position where a number should be inserted. After locating this position, we only need to move this position and the following elements to the right by one bit, and The vacated position can be directly inserted into the first element in the unordered area, which reduces the number of comparisons.

public static void binaryInsertionSort(int[] arr) {
    int count1 = 0;//记录循环次数
    int count2 = 0;//记录交换次数
    for (int i = 1; i < arr.length; i++) {// 从无序区第一个元素开始迭代
        count1++;
        int temp = arr[i];// 记录无序区第一个元素的值
        int index = binarySearch(arr, temp, 0, i);
        for (int j = i; j > index; j--) {
            count2++;
            arr[j] = arr[j - 1];
        }
        arr[index] = temp;
    }
    System.out.println("binaryInsertionSort:循环次数" + count1 + ",交换次数:" + count2);
}
private static int binarySearch(int[] arr, int target, int start, int end){
    int range = end - start;
    if (range > 0) {
        int mid = (start + end) / 2;
        if (arr[mid] > target) {
            return binarySearch(arr, target, start, mid - 1);
        }else if (arr[mid] < target) {
            return binarySearch(arr, target, mid + 1, end);
        }else {
            return mid + 1;
        }
    }else {
        if (arr[start] > target) {
            return start;
        }else {
            return start + 1;
        }
    }
}

The output result is:
binaryInsertionSort: number of loops 9, number of exchanges: 26.

4. Merge Sort

Merge sort is a very typical application of Divide and Conquer. Merge the ordered subsequences to get a completely ordered sequence; that is, make each subsequence ordered first, and then make the subsequences ordered. Merging two sorted arrays into one sorted array is called a binary merge.

The idea of ​​merge sort:

Split an array into two halves, sort each half separately, and use the merge operation to merge the two sorted subarrays into a single sorted array. We can divide an array into two at first, that is, 2 1/2s, then divide each half into two halves, which is 4 1/4s, and so on, repeatedly dividing the array until we get a sub-array Contains only one data item, which is the base value condition, and a subarray with only one data item must be ordered.

The sorting process is shown in the figure:
write picture description here

@Test
public void test(){
    int[] arr = new int[] { 10, 5, 3, 8, 2, 4, 9, 1, 7, 6 };
    mergeSort(arr, 0, arr.length - 1);
    System.out.println("mergeSort:循环次数" + count1 + ",交换次数:" + count2);
}
private static int count1 = 0;//记录循环次数
private static int count2 = 0;//记录交换次数
/*
 * 递归拆分数组
 */
public static void mergeSort(int[] arr, int begin, int end) {
    if (end > begin) {
        int mid = (begin + end) / 2;
        mergeSort(arr, begin, mid);
        mergeSort(arr, mid + 1, end);
        merge(arr, begin, end);
    }
}
/*
 * 拆分后进行排序比较
 */
private static void merge(int[] arr, int startIndex, int endIndex) {

    int mid = (startIndex + endIndex) / 2;// 将数组分成两个数组,两个数组内部已排序
    int leftStartIndex = startIndex;// 左边数组,已排序
    int rightStartIndex = mid + 1;// 右边数组,已排序
    int hasMerge = 0;
    int temp[] = new int[endIndex - startIndex + 1];// 创建临时数组还存储比较后的数据
    while (leftStartIndex <= mid && rightStartIndex <= endIndex) {// 两个数组依次比较
        count1++;
        if (arr[leftStartIndex] < arr[rightStartIndex]) {
            temp[hasMerge++] = arr[leftStartIndex++];
        } else {
            temp[hasMerge++] = arr[rightStartIndex++];
        }
    }
    while (leftStartIndex <= mid) {// 如果左边数组还有余值,依次放入临时数组中
        count1++;
        temp[hasMerge++] = arr[leftStartIndex++];
    }
    while (rightStartIndex <= endIndex) {// 如果右边数组还有余值,依次放入临时数组中
        count1++;
        temp[hasMerge++] = arr[rightStartIndex++];
    }
    hasMerge = 0;
    while (startIndex <= endIndex) {// 将排序后的临时数组拷贝进原数组中
        count2++;
        arr[startIndex++] = temp[hasMerge++];
    }
}

The output result is:
mergeSort: number of cycles: 34, number of exchanges: 34.

Algorithm time complexity analysis:

The best, worst and average time complexity of merge sort is O(nlogn), while the space complexity is O(n), the number of comparisons is between (nlogn)/2 and (nlogn)-n+1, ​​assignment operation The number of times is (2nlogn).

Algorithm stability analysis:

The merge sort algorithm is more memory-intensive, but it is an efficient and stable sorting algorithm.

5. Quick sort

There is no doubt that quicksort is the most popular sorting algorithm, and for good reason, in most cases, quicksort is the fastest, with an execution time of the order O(N*logN).

The quicksort algorithm essentially works by patitioning an array into two subarrays, then recursively calling itself to partition the subarrays into finer subarrays, and do quicksort for each subarray.

In the merge sort explained earlier, the array is actually split and then sorted. Quick sort and merge sort are typical applications of divide and conquer. The difference, however, is that the criteria for the division of the two are different:

In merge sort , the array is continuously divided into two sub-arrays of the same size. , 7, 6} are divided into {10, 5, 3, 8, 2} and {4, 9, 1, 7, 6}.

In quicksort , the division of the array is based on a certain pivot value. When splitting, all elements greater than the pivot value are placed in one group, and all elements smaller than the pivot value are placed in another group. For example, if the reference value is 5, then {10, 5, 3, 8, 2, 4, 9, 1, 7, 6} will be divided into {1, 5, 3, 4, 2,} and {8, 9 , 10, 7, 6}.

It should be noted that the merge sort is divided according to the median value of the subscript of the array, so the length of the two sub-arrays after division differs by at most 1, but for quick sort, it is divided according to the reference value, if the reference value is selected If the reference value is equal to 1, then {10, 5, 3, 8, 2, 4, 9, 1, 7, 6} are divided into {1} and {10, 5, 3, 8, 2, 4, 9, 7, 6}, so in quicksort we need to pay special attention to the division operation.

The division algorithm is done by two pointers, the two pointers point to the beginning and the end of the array, the left pointer moves to the right and the right pointer moves to the left. When the left pointer encounters a value less than the base value, it continues to move right, because the position of the data item is already on the less-than base value side of the array. When it encounters a number larger than the reference value, it stops; when the right pointer encounters a number larger than a certain value, it continues to move left, and when it encounters a number smaller than the reference value, it stops. When both stopped, both the left pointer and the right pointer point to the data item on the wrong side of the array, so the two data items are swapped.

After the swap, keep moving the two pointers, then stop at the appropriate actual, swap data, and repeat the process. The division is complete when the right pointer and the left pointer meet.
write picture description here

Regarding the choice of the reference value among the three data items
, ideally, the median value of the data items should be selected as the pivot, that is, half of the data items should be larger than the pivot, and half of the data items should be smaller than the pivot, which will make the array is divided into two equal-sized subarrays. In order to avoid the pivot to select the largest or smallest value, a compromise method is adopted, which finds the data in the first, last and middle positions in the array, and selects the data in the middle position as the reference value. This method is called "three" The data item is selected".

@Test
public void test(){
    int[] arr = new int[] {10, 5, 3, 8, 2, 4, 9, 1, 7, 6};
    quickSort(arr, 0, arr.length - 1);
    System.out.println("quickSort:循环次数" + count1 + ",交换次数:" + count2);
}
private static int count1 = 0;//记录循环次数
private static int count2 = 0;//记录交换次数
/**
 * 划分数组
 */
public static void quickSort(int[] arr, int start, int end) {
    if (start >= end) {
        return;
    }
    int partitionIndex = partition(arr, start, end);
    quickSort(arr, start, partitionIndex);
    quickSort(arr, partitionIndex + 1, end);
}
/**
 * 返回划分后,左指针和右指针相遇的下标
 */
private static int partition(int[] arr, int start, int end) {
    int pivot = getPivot(arr, start, end);
    int left_pointer = start - 1;
    int right_pointer = end + 1;
    while (true) {
        while (arr[++left_pointer] < pivot){
            count1++;
        };// left_pointer当遇到比基准值大的元素,停下来
        while (arr[--right_pointer] > pivot){
            count1++;
        };// right_pointer当遇到比基准值小的元素,停下来
        if (left_pointer >= right_pointer) {
            break;
        }
        swap(arr, left_pointer, right_pointer);
    }
    return right_pointer;
}
/**
 * 获得基准值
 */
private static int getPivot(int[] arr, int start, int end) {
    int median = arr[start];
    if (end - start >= 2) {// 保证有3个数据项
        int left = arr[start];
        int right = arr[end];
        int middle = arr[(start + end) / 2];
        if (left < middle && middle < right) {
            median = middle;
        } else if (left < right && right < middle) {
            median = right;
        } else {
            median = left;
        }
    }
    return median;
}
/**
 * 交换数组中两个元素的位置
 */
private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    count2++;
}

The output is:
mergeSort: 20 cycles, 8 exchanges.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325488487&siteId=291194637