基础排序算法学习笔记(持续更新...)

基础排序算法(持续更新…)

前辈们教导我:“算法和数据结构之于我们程序员就好比任督二脉之余习武之人。掌握扎实的算法和数据结构基础就好比打通了任督二脉的武侠,练起功来那可是事半功倍滴。”于是作为程序员小鸟,亦是不敢稍有松懈,特建立此笔记文档巩固近来所学并方便日后复习,也或许可以帮到同在学习的小鸟们。

冒泡排序

冒泡排序的核心思想就是拿到数组最末尾的数,跟它的上一位数比较大小。如果当前数小于上一位数的值,则互换位置,游标往上移;否则就直接移动游标到上一位;继续循环这个过程,把小的数往上“冒”,直到数组从小到大排好的过程,就是冒泡排序
###代码如下:
无优化版本:有助于理解,逻辑通了再看优化的也简单明了。
通过两个循环实现冒泡。外层循环控制取几次数组末尾的数去与整个数组进行比较,而内层循环控制你从数组末尾取到的这个数要去进行多少次的大小比较。

public static void BubbleSort(int[] k)
{
	int n = k.Length; //获取一下长度方便后面使用
	for(int i = 0; i < n - 1; i++) //外层循环
	{
		for(int j = n - 1; j > 0; j--) //内层循环
		{
			if(k[j]<k[j-1]) //拿当前数去比大小
			{
				//交换两个数的经典代码
				int temp = k[j];
				k[j] = k[j-1];
				k[j-1]= temp;
			}
		}
	}
}

优化:核心思想就是对两个循环的循环次数控制;现在内层循环是每次拿到数组最后一个数后必须跟整个数组的全部数都比较一次。实际情况是内层循环每完成一次循环,数组中的安全区(此处的安全区是数组最前面的数)便会扩大一位,因此实际要比较的数就也少了一位。以下用伪代码解释一下:

数组 k = [4,6,8,3,5,1]
完成第一轮内层循环后:k = [1,4,6,8,3,5]
完成第二轮内层循环后:k = [1,3,4,6,8,5]
可以看到在完成第一轮内层循环后,数组中前面第一个数“1”必定为最小值,也就是说我们可以不需要再去跟它比较都知道“1”会比我们数组中后面其他数都小,因此这里“1”就是我们的安全区;同理,完成第二轮内层循环后,此时“1,3”都是我们的安全区,我们不需要跟“3”去比较因为它必定比它后面任何数都小。

在看懂了安全区的逻辑后,第一个优化基本就掌握了。接着是第二个优化,即对外层循环的优化。

这个更加易懂,举个例子就能明白了:
数组 k = [1,2,3,4,5]
k已经是完美从小到大排好序的数组,但是如果你把它传入我们上面写的冒泡排序方法中,这个已经被排好序的数组仍然会无奈的把内外环循环都跑一遍,相当于白白做了4x4= 16次大小的对比。显而易见我们是想避免这样的情况的。
解决办法也非常简单,只要添加一个状态值去记录整个内层循环是否有进行过一次有意义的大小比较,即是否在对比中发现比拿到的数大的数从而发生了值的交换。如果内层循环跑完一轮,却没有发生任何交换即代表整个数组都在正确的排序中,因此可以直接终止循环。

说了这么多,代码如下:

public static void BubbleSort(int[] k)
{
	int n = k.Length; //获取一下长度方便后面使用
	bool isSwapped = true; //记录交换状态的布尔值:优化二;
	for(int i = 0; i < n - 1 && isSwapped; i++) //外层循环
	{
		isSwapped = false;
		for(int j = n - 1; j > i; j--) //内层循环:优化一;
		{
			if(k[j]<k[j-1]) //拿当前数去比大小
			{
				//稍稍换了一个交换逻辑
				k[j] = k[j] + k[j-1];
				k[j-1] = k[j] - k[j-1];
				k[j] = k[j] - k[j-1];
			}
		}
	}
}

顺带说一下,我的这个冒泡是从小到大排,从后往前冒,你当然也可以写从大到小排,从前往后落。不过我觉得既然叫冒泡,那肯定是要往上冒的嘛,哈哈。

插入排序

核心思想其实就是在数组中获取一个数然后往前比大小找到合适的位置把该值插入到数组中。比较形象的比喻是抓牌然后把新抓到的牌找到合适的位置插到手牌里。

我个人感觉插入排序其实和冒泡排序有异曲同工之处:都是获取一个数后进行大小比较进而找出它应该排序的位置的过程。区别在于冒泡排序中内层循环中,你拿去比较的这个数是有可能变化的,而插入排序中,我们提前把这个要去比较的数的值存了起来,在找到它应该插入的位置后再进行插入的操作
学过冒泡之后看插入应该非常简单,注意好他们两个的区别即可。废话不多说,直接上代码。

代码如下:

public static void InsertSort(int[] k)
{
	int j;//内层循环的控制变量
	int temp; //用于记录插入值
	int n = k.Length;
	for(int i = 0; i < n; i++)
	{
		j = i + 1; //
		temp = k[j];//记录获取到的值
		while(j > 0 && temp < k[j-1])
		{
			k[j] = k[j-1]; //把较大的值往后放
			j--; //前移游标
		}
		k[j] = temp; //插入
	}
}

快速排序

这个名字牛的飞起的排序算法核心思想就是在数组中获取到一个数,然后把比这个数大的放到数组右边,比它小的放到数组左边,最后这两组数相接的地方就是这个数应该排序的位置。难点就是移动大数和小数的过程然后对这两组数再次进行分大小的过程。解决这个难点有两种方法,一种就是while循环另一种就是回调函数(Recursion)。本文将会用回调函数编写。

代码如下

namespace QuickSort
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] k = new int[] { 6, 7, 2, 4, 9, 5, 1, 8, 3 };
            Console.WriteLine("********Before the Quick Sort********");
            Print(k);
            Console.WriteLine();
            Console.WriteLine("********After the Quick Sort********");
            QuickSort(k, 0, k.Length - 1);
            Print(k);
            Console.ReadKey();
        }

        /// <summary>
        /// Print out all element in an array
        /// </summary>
        /// <param name="k"></param>
        static void Print(int[] k)
        {
            foreach (int item in k)
            {
                Console.Write(item + " ");
            }
        }


        /// <summary>
        /// Swap two elements in an array
        /// </summary>
        /// <param name="k">Thy array</param>
        /// <param name="first">the first element</param>
        /// <param name="second">the second element</param>
        static void Swap(int[] k, int first, int second)
        {
            int temp = k[first];
            k[first] = k[second];
            k[second] = temp;
        }


        /// <summary>
        /// Quick Sorting an array
        /// </summary>
        /// <param name="k">Thy array</param>
        /// <param name="start">Starting index of the sort</param>
        /// <param name="end">Ending index of the sort</param>
        static void QuickSort(int[] k, int start, int end)
        {
            if (start < end) //just to protect the recursion in case of a dead loop
            {
                while (start < end) //Optimazation 2
                {
                    int pointer = Partition(k, start, end); //find the pointer between start and end
                    if (pointer - start < end - pointer)
                    {
                        QuickSort(k, start, pointer - 1); //Sort again on the numbers on the left of the pointer
                        start = pointer + 1;
                    }

                    else
                    {
                        QuickSort(k, pointer + 1, end); //on the right of the pointer
                        end = pointer - 1;
                    }
                }
            }
        }

        /// <summary>
        /// To sort the array based on the value of the pointer
        /// where all value smaller than the pointer should be to the left of the pointer
        /// and all value larger than the pointer is to the right of the pointer
        /// </summary>
        /// <param name="k">Thy array</param>
        /// <param name="left">Left most index</param>
        /// <param name="right">Right most index</param>
        /// <returns></returns>
        static int Partition(int[] k, int left, int right)
        {
            int i = left;
            int j = right;

            //Quick Sort optimization : 
            int m = (i + j) / 2;
            if (k[i] > k[j])
            {
                Swap(k, i, j);
            } //to make sure k[j] is always larger than k[i]

            if (k[m] > k[j])
            {
                Swap(k, m, j);
            }//to make sure k[j] is always larger than k[m]

            if (k[m] > k[i])
            {
                Swap(k, i, m);
            }//this is to make sure k[i] will not be the smallest number in the array

            int p = k[i]; //pick the first element from left as the pointer

            while (i != j)
            {
                //since we pick the first left element as pointer
                //we will start the search from the right for obvious reason(:P)
                while (i < j && k[j] > p)
                {
                    //when we are in the iteration, it means the element k[j] is larger than p
                    //which is what we want so we will keep searching 
                    j--;
                }

                while (i < j && k[i] <= p)
                {
                    //vice versa
                    i++;
                }

                //when we are here: we find two elements k[j] < p and k[i] >p
                //since we want numbers smaller than p be at the left and those that are larger to the right
                //so we simply just switch them to put them where they ought to be
                Swap(k, i, j);
            }

            //Here means i = j, so we know the search is finished
            //we are at a place where all element to the left is smaller than p
            //all element to the right is larger than p
            //so this is where p supposed to be
            Swap(k, left, i); //here i or j doesnt matter since i =j;
            return i; //same here that i or j doesn't matter
        }


    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kayn_Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值