算法导论阅读记录

\(\color{red}{不正确的算法如果其错误率可以被控制的情况下肯是很有用的}\)

动态图解排序算法

插入排序

对少量元素的排序较为有效,每次选择一个待排序元素,依次与已排序集合比较

伪代码 ``` //从第2个元素开始比较 for(i=2;i0 && arr[j]>value;j--) arr[j+1]=arr[j] //当元素已经找到合适插入位置时,将本次计算的arr[i]插入到因移动位置所空出来的合适位置 //如果本次循环没有发生移动,则将j放置在集合最末尾处,通过一开始的j=i-1,再加上这里的j+1即可 arr[j+1]=value ``` 个人理解:在插入排序的这个实现中,由于需要对数据进行移动操作,会耗费比较多的时间,最坏情况下每一个元素都需要进行移动, 所以在元素较少的时候来使用是比较合适的。

希尔排序

希尔排序是对插入排序的改进,在插入排序的基础上添加了缩小增量的逻辑,所有也称缩小增量排序,以提出的DL.Shell的名字命名。
通过每次缩小增量知道最后一次排序,简单的处理可以以初始gap = arr/2,后续循环则为 gap/=2

/// <summary>
        /// 希尔排序
        /// </summary>
        /// <param name="arr"></param>
        static void ShellSort(int[] arr)
        {
            //控制间隔
            for (int gap = arr.Length / 2; gap > 0; gap = gap / 2)
            {
                //对数据进行分组
                for (int i = gap; i < arr.Length; i++)
                {
                    int temp = arr[i];
                    int j = i - gap;
                    //将大的数后移
                    for (; j >= 0; j -= gap)
                    {
                        if (arr[j] > temp)
                        {
                            arr[j + gap] = arr[j];
                        }
                        else
                        {
                            break;
                        }
                    }

                    //将当前需要插入的数据放到最后一次移动的位置
                    //因为上方会多减去一个gap的值才能退出循环所以此处要再补上
                    arr[j + gap] = temp;
                }
            }
        }

归并

归并排序是稳定排序,复杂的nlogn - 一共需要拆分成 logn层树,每一层都需要将n个对象进行排序
归并排序需要分配n个存储空间,即空间复杂度 O(n)。
归并排序适用于数据量大且对稳定性有要求的场景

方法一:递归

/// <summary>
        /// 归并
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public static void MergeSort(int[] arr, int start, int end)
        {
            var len = (end - start) + 1;
            if (len < 2)
            {
                return;
            }
            if (start == end || start > end)
            {
                return;
            }

            if (len == 2)
            {
                if (arr[start] > arr[end])
                {
                    var temp = arr[start];
                    arr[start] = arr[end];
                    arr[end] = temp;
                }

                return;
            }

            int midLength = (end - start) / 2;
            //if ((end - start) % 2 == 1)
            //{
            //    length++;
            //}
            MergeSort(arr, start, start + midLength);
            MergeSort(arr, start + midLength + 1, end);

            //两端已经有序
            //归并

            var result = new int[end - start + 1];
            Array.Copy(arr, start, result, 0, end - start + 1);
             
            int i = 0, j = midLength + 1;
            for (int k = 0; k < result.Length; k++)
            {
                if (i < midLength + 1 && j < result.Length)
                {
                    if (result[i] < result[j])
                    {
                        arr[start + k] = result[i++];
                    }
                    else
                    {
                        arr[start + k] = result[j++];
                    }
                    continue;
                }
                if (i < midLength + 1)
                {
                    arr[start + k] = result[i++];
                }
                if (j < result.Length)
                {
                    arr[start + k] = result[j++];
                }
            }
        }

方式二 从底部向上归并


        /// <summary>
        /// 归并 - 从树根往上合并
        /// </summary>
        /// <param name="arr"></param>
        public static void MergeSortFromEnd(int[] arr)
        {
            int gap = 2;
            while (gap < arr.Length)
            {
                for (int i = 0; i < arr.Length; i += gap)
                {
                    MergeArray(arr, i, i + gap - 1);
                }
                gap *= 2;
            }
            MergeArray(arr, 0, gap - 1);
        }

        private static void MergeArray(int[] arr, int start, int end)
        {
            if (start > arr.Length - 1)
            {
                return;
            }

            if (end - start + 1 == 2)
            {
                if (arr[start] > arr[end])
                {
                    var temp = arr[start]; arr[start] = arr[end]; arr[end] = temp;
                }

                return;
            }

            int midLength = (end - start) / 2;

            //两端已经有序
            //归并

            int currentLength = 0;
            int[] result = null;
            if (end + 1 > arr.Length)
            {
                currentLength = arr.Length - start;
            }
            else
            {
                currentLength = end - start + 1;
            }
            result = new int[currentLength];

            Array.Copy(arr, start, result, 0, currentLength);
            int i = 0, j = midLength + 1;
            for (int k = 0; k < result.Length; k++)
            {
                if (i < midLength + 1 && j < result.Length)
                {
                    if (result[i] < result[j])
                    {
                        arr[start + k] = result[i++];
                    }
                    else
                    {
                        arr[start + k] = result[j++];
                    }
                    continue;
                }
                if (i < midLength + 1)
                {
                    arr[start + k] = result[i++];
                }
                if (j < result.Length)
                {
                    arr[start + k] = result[j++];
                }
            }
        }

快速排序

核心思想是选定一个基准的数值(称这个值为枢轴),然后将待排元素中大于基准的数放到右侧,小于基准的数放到左侧,然后再递归两侧的元素
也可以理解为把所有小于枢轴的数放到左侧,然后递归。
快排和归并都是分治法。
快排的核心是枢轴的选取,基础的办法是每次选择第一个元素,简单一点的处理方法是待排元素中随机取一个枢轴
时间复杂度为O(nLogN)

public static void QuickSort(int[] arr, int start, int end)
        {
            int temp;
            if (start >= end)
            {
                return;
            }

            //枢轴
            int pivot = arr[start];
            //下一个元素存储的位置
            int saveIndex = start + 1;

            for (int i = saveIndex; i <= end; i++)
            {
                if (pivot > arr[i])
                {
                    temp = arr[i];
                    arr[i] = arr[saveIndex];
                    arr[saveIndex] = temp;
                    saveIndex++;
                }
            }

            //做一次交换,将枢轴调整到两侧元素的中间
            temp = arr[start];
            arr[start] = arr[saveIndex - 1];
            arr[saveIndex - 1] = temp;

            QuickSort(arr, start, saveIndex - 1);
            QuickSort(arr, saveIndex + 1, end);
        }

随机化快速排序

随机的逻辑可以在排序前随机的将一个数交换到待排元素的最左侧即可


        /// <summary>
        /// 随机化快速排序
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public static void RandomQuickSort(int[] arr, int start, int end)
        {
            int temp;
            if (start >= end)
            {
                return;
            }

            //随机选一个枢轴
            var random = new Random();
            var pivotIndex = random.Next(start, end);
            //随机取一个数作为枢轴
            temp = arr[start];
            arr[start] = arr[pivotIndex];
            arr[pivotIndex] = temp;


            //枢轴
            int pivot = arr[start];
            //下一个元素存储的位置
            int saveIndex = start + 1;

            for (int i = saveIndex; i <= end; i++)
            {
                if (pivot > arr[i])
                {
                    temp = arr[i];
                    arr[i] = arr[saveIndex];
                    arr[saveIndex] = temp;
                    saveIndex++;
                }
            }

            //做一次交换,将枢轴调整到两侧元素的中间
            temp = arr[start];
            arr[start] = arr[saveIndex - 1];
            arr[saveIndex - 1] = temp;

            QuickSort(arr, start, saveIndex - 1);
            QuickSort(arr, saveIndex , end);
        }

此方法是在随机的基础上将小于枢轴的数调整到左侧、大于枢轴的数调整到右侧。
如果在有大量重复元素的情况下使用随机化的方法将导致两侧元素数量极不均衡,从而导致退化成O(n²),这种情况下可以使用双路快速排序

双路快速排序


        /// <summary>
        /// 双路快速排序
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public static void RandomQuickSortBy2Way(int[] arr, int start, int end)
        {
            int temp;
            if (start >= end)
            {
                return;
            }

            //随机选一个枢轴
            var random = new Random();
            var pivotIndex = random.Next(start, end);
            //随机取一个数作为枢轴
            temp = arr[start];
            arr[start] = arr[pivotIndex];
            arr[pivotIndex] = temp;


            //枢轴
            int pivot = arr[start];
            //下一个元素存储的位置
            int leftSaveIndex = start + 1;
            int rightSaveIndex = end;

            while (true)
            {
                //将小于枢轴的数从左往右存
                while (leftSaveIndex < end && arr[leftSaveIndex] < pivot)
                {
                    leftSaveIndex++;
                }

                //将大于枢轴的数从右往左存
                while (rightSaveIndex > start + 1 && arr[rightSaveIndex] > pivot)
                {
                    rightSaveIndex--;
                }

                //遍历结束
                if (leftSaveIndex > rightSaveIndex)
                {
                    break;
                }

                //调整左侧大于枢轴和右侧小于枢轴的数 - 调整后将索引对应向前向后移动以便继续调整后续的待排元素
                temp = arr[leftSaveIndex];
                arr[leftSaveIndex++] = arr[rightSaveIndex]; 
                arr[rightSaveIndex--] = temp;
            }

            //做一次交换,将枢轴调整到两侧元素的中间
            temp = arr[start];
            arr[start] = arr[leftSaveIndex - 1];
            arr[leftSaveIndex - 1] = temp;

            QuickSort(arr, start, leftSaveIndex - 1);
            QuickSort(arr, leftSaveIndex, end);
        }

三路快速排序

此方法是在双路快速排序的基础上对和枢轴值相等的数据做了处理,等于枢轴的值在下次递归中不再进行处理。
此方法适用于存在大量重复数据的场景,避免了大于或小于枢轴的数据出现某一个元素特别多的情况


        /// <summary>
        /// 三路快速排序
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        public static void RandomQuickSortBy3Way(int[] arr, int start, int end)
        {
            if (start >= end)
            {
                return;
            }
            int temp;

            //随机选一个枢轴
            var random = new Random();
            var pivotIndex = random.Next(start, end);
            //随机取一个数作为枢轴
            temp = arr[start];
            arr[start] = arr[pivotIndex];
            arr[pivotIndex] = temp;

            //枢轴
            int pivot = arr[start];
            //下一个元素存储的位置
            int leftIndex = start;
            //右侧下一个元素存储的位置
            int rightIndex = end;
            int i = leftIndex + 1;
            while (i <= rightIndex)
            {
                //小于枢轴
                if (arr[i] < pivot)
                {
                    temp = arr[i];
                    arr[i] = arr[leftIndex];
                    arr[leftIndex] = temp;

                    leftIndex++;
                    i++;
                }
                //大于枢轴
                else if (arr[i] > pivot)
                {
                    temp = arr[i];
                    arr[i] = arr[rightIndex];
                    arr[rightIndex] = temp;

                    rightIndex--;
                }
                //等于枢轴
                else
                {
                    i++;
                }
            }

            QuickSort(arr, start, leftIndex - 1);
            QuickSort(arr, rightIndex + 1, end);
        }

快速排序可以在寻找第n大的元素时候使用,只需要在排序后调整枢轴位置处判断
if(th==leftSaveIndex ){
return arr[leftSaveIndex ];
} else if (th>leftSaveIndex ){
//从右侧寻找
}
else{
//从左侧寻找
}

堆排序

堆的插入,从堆的最后一个元素后加入,随后再进行排序,O(logn)
shiftup

//新增的元素加入到最后一个节点,随后依次向上比较,如果大于父节点则交换元素
static void ShiftUp(List<int> arr, int newValue)
        {
            arr.Add(newValue);
            for (int i = arr.Count - 1; i > 0 && arr[i] > arr[i / 2]; i /= 2)
            {
                    var temp = arr[i];
                    arr[i] = arr[i / 2];
                    arr[i / 2] = temp;
            }
        }

计数排序

已知数据范围且重复数据较多时使用,利用数组下标做索引,空间换时间

static void CountingSort(int[] arr, int min, int max)
        {
            //计算范围
            var result = new int[max - min + 1];
            foreach (int i in arr)
            {
                result[i - min]++;
            }
            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }

基数排序

针对数值型数据使用的特殊排序,先对低位进行排序,依次处理到高位

static void BaseSort(int[] arr)
        {

            var max = arr.Max();

            int maxDigit = 1;

            while (maxDigit <= max * 10)
            {
                arr = BaseSort(arr, maxDigit);
                maxDigit *= 10;
            }

            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
        }

        /// <summary>
        /// 根据基数排序 - 非原地
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="maxDigit"></param>
        /// <returns></returns>
        static int[] BaseSort(int[] arr, int maxDigit)
        {
            Dictionary<int, List<int>> baseData = new();
            //初始化临时结构
            for (int i = 0; i < 10; i++)
            {
                baseData.Add(i, new List<int>());
            }

            foreach (var item in arr)
            {
                var mod = item / maxDigit % 10;
                baseData[mod].Add(item);
            }

            int j = 0;
            var result = new int[arr.Length];
            foreach (var baseSortList in baseData)
            {
                foreach (var item in baseSortList.Value)
                {
                    result[j++] = item;
                }
            }

            return result;
        }

posted @ 2023-05-25 16:00  Hey,Coder!  阅读(4)  评论(0编辑  收藏  举报