黑马程序员----实用的数组排序查找

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一、排序算法

所谓 排序 就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作 排序算法 ,就是如何使得记录按照要求排列的方法。

排序算法列表:

在此我们主要介绍使用其中的两种,那就是 冒泡排序选择排序,其实 速度最快的是希尔排序,希尔排序采用三层循环+位运算

冒泡排序:

一次一次的遍历集合(个数由第二层循环控制),每次比较相邻两个元素,如果它们当前顺序错误就交换它们的位置,然后再往后继续如此直到倒数第二个元素跟最后一个元素比较,到此一次遍历结束。我们可以看到,如果我们想从小到大排序,那么一轮遍历后,最后一个元素应该就是最大的那个元素,而其他位置的不一定有序。因此我们开始第二次遍历,这次遍历的具体步骤与第一次一致,但是问题的规模小了,只要循环遍历到倒数第三个和第二个比较即可。因为每一次排序都有一个当前最大的元素浮动到当前范围的最后一个位置,就好像大的泡泡先浮出水面一样,因此取名冒泡排序。
特点:冒泡排序的效率并不是非常高,但是由于其代码简单,在不是非常要求性能的地方使用也比较普遍。
冒泡排序代码:
/**
冒泡排序:
相邻位置的比较,值向两边冒
*/
class MaoPao
{
	public static void main(String[] args)
	{
		int[] arr={12,2,4,56,7,8,12,24,34,67,4};
		for(int i=0;i<arr.length-1;i++)
		for(int j=0;j<arr.length-i-1;j++)//重点:每次比较的长度减少一个,因为每次都能确定一个位置的值
		{
			if(arr[j]>arr[j+1])
			{
			arr[j]^=arr[j+1];arr[j+1]^=arr[j];arr[j]^=arr[j+1];
			}
		}
		for(int i=0;i<arr.length;i++)
		{
			System.out.print(arr[i]+" ");
		}
		System.out.println();
	}
}
运行图:
小结:对于冒泡排序算法,重点在于第二层循环的循环控制条件那里应该是随着外层循环的进行而由所变化的,也就是每次外层循环进行一次,第二层循环循环的元素个数应该减少一个。

选择排序:

每次取出当前位置的元素,从0号位置开始,将其后所有元素与其进行比较,如果有比它小的,让指针指向这个更小的位置,继续向后比较,直到最后一个元素,如果此时指针指向的位置不是最开始的当前元素位置(例如此时的0),那么就交换指针指向的位置的元素和最开始的当前位置的元素(例如此时的0),到此一轮循环结束。第二轮循环从1开始,以此类推,直到倒数第二个元素进行一轮循环后结束。每次循环遍历我们都能将当前序列的最小值(假设从小到大排序)取出来放到当前序列最前面位置。当我们处理完倒数第二个元素时,集合自然就有序了。
特点:这种方法的特点主要是减少了很多无谓的交换元素的操作,如果有n个元素,那么最多也就是交换n-1次而已,效率比之冒泡排序要高。

选择排序代码:

/**
选择法排序
*/
class Paixu
{
	public static void main(String[] args)
	{
		int[] arr={12,2,9,34,3};
		f(arr);
		for(int i=0;i<arr.length;i++)
		{
			System.out.print(arr[i]+" ");
		}
		System.out.println();
	}
	private static void f(int[] arr)
	{
		for(int i=0;i<arr.length-1;i++)
		{
			int k=i;
			for(int j=i+1;j<arr.length;j++)
			{
				if(arr[j]>arr[k])k=j;//重点语句
			}
			if(k!=i)
			{
				arr[k]=arr[k]^arr[i];
				arr[i]=arr[k]^arr[i];
				arr[k]=arr[k]^arr[i];
			}
		}
	}
}
运行图:


小结:对于选择排序,在交换元素时,我们选择位运算的方式提高效率,该算法的重点在于每次内循环都要达到将最大值记录下来,在循环结束后,与当前元素比较,如果不是同一个位置,那么就交换,也就把当前的最大值放到了当前序列最左边。


算法总结:

算法和数据结构一样,都有其适合的地方,不是每个算法都适合所有地方,有的算法在某种情况下效率高,但是到了其他情况下可能效率低也很正常,因此使用算法主要还是要看当时的需求而定。


二、查找算法

查找算法效率比较:


在此我们主要介绍的是折半查找。

折半查找:

折半查找的前提是序列必须是有序的,要么从小到大,要么从大到小。具体思想是:首先设置两个指针指向序列头尾,每次都将当前Key与当前序列的中间位置的元素比较,在此我们假设序列从小到大,如果中间值等于Key,那么就找到了,返回位置。如果没找到,且中间值大于Key,那么说明我们应该到中间值左边查找,就将尾指针指向中间值前一个元素,如果中间值小于Key,说明我们应该到中间值右边去查找,就将头指针指向中间值的后一个元素。然后继续拿中间值比较,处理三种情况。当出现头指针大于尾指针时,说明已经查找完毕了,这时就跳出循环,返回-1,表示集合中不存在Key。
特点:折半查找的特点首先是它要求序列有序,因此通常需要配合排序算法一起用。由于其每次都能减少当前序列一半的元素,因此效率极高。

折半查找代码:

/**
折半查找
数组是有序的
*/
class HalfSearch
{	//数组递增
	private static int f(int[] arr,int key)
	{
		for(int min=0,max=arr.length-1,mid=(min+max)/2;min<=max;)
		{
			if(arr[mid]==key)return mid;
			else if(arr[mid]>key)
			{
				max=mid-1;				
			}
			else
			{
				min=mid+1;
			}
			mid=(min+max)/2;
			
		}
		return -1;
	}
	public static void main(String[] args)
	{
		int[] arr={2,3,5,7,8,9,12,34};
		int index=f(arr,3);
		System.out.println("元素3在数组中的位置:"+index);
	}
}
运行图:


小结:对于折半查找,首先一定要继续需要集合有序才能使用,算法中最重要的就是跳出循环的条件,那就是min>max。

查找算法总结:

查找算法用于查找集合中的某个元素,使用是非常频繁的,同样的对于查找算法,每个算法都有自己适合的地方,关键是看需求。例如:如果序列有序,那么我们肯定要使用折半查找算法,这种算法实现简单,而且效率高。


三、查表法

这不是哪一个算法,不是排序也不是查找,而是一种处理问题的思想。我们知道数组最方便我们的地方在于它的下标,而它的下标通常没有实际意义,就是个索引,如果我们将它的下标赋予意义呢?或者说本来在某些问题上,也可以使得它的下标有意义。

进制转换:

当我们想将10进制转成16进制时,如何将10转为A,11转为B等问题是比较麻烦的,如果使用if活着switch语句也能解决,但是代码显得很冗余。此时我们可以考虑查表法。 思路:关键在于将10和A联系起来,那就简单了,我们可以将10作为下标,而在数组中这个位置存入A,这就将10和A联系了起来,其他位置也一样。
进制转换代码:
/**
十进制转十六进制
*/
class TenToHex
{
	public static void main(String[] args)
	{
		System.out.println(Integer.toBinaryString(1230));
		System.out.println(toHex(1230));//使用StringBuffer作为容器
		char[] arr=toHex_2(1230);//使用数组作为容器
		for(int i=0;i<arr.length;i++)
		{
			if(arr[i]!='\u0000')//char在堆中默认值为'\u0000'
				System.out.print(arr[i]);
		}
		System.out.println();
	}
	private static StringBuffer toHex(int num)
	{
		//使用StringBuffer作为容器
		StringBuffer sb = new StringBuffer();
		while(num>0)
		{
			int temp=num&15;
			if(temp<10)
			{
				sb.append((char)(temp+'0'));	
			}
			else
			{
				sb.append((char)(temp-10+'a'));
			}
			num=num>>>4;
		}
		return sb.reverse();
	}
	private static char[] toHex_2(int num)
	{
		//使用数组作为容器,查表法得到对应的char值
		char[] chs={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
		char[] arr=new char[8];
		int i=7;
		while(num>0)
		{
			int temp=num&15;
			arr[i--]=chs[temp];
			num=num>>>4;
		}
		return arr;
	}
}
运行图:

小结:我们可以看到不使用查表法也可以做到进制转换,但是从代码简洁和可读性的角度来说,都是查表法更胜一筹。因此我们在这种情况下应该使用查表法。

计算天数:

例如给出今天是6月23号,那么请问今天是今年的第多少天,这个问题也涉及到查表法的使用,我们可以定义13长度的数组,位置0不用(便于理解),从1开始,每个位置的下标表示月份,而该位置存放的是该月的天数,例如位置1,表示1月,存放31,因为1月是31天,依次类推。
计算天数代码:
//计算2015.06.23是今年第几天
class  CountDays
{
	private static void f(int year,int month,int day)
	{
		//年份主要用来计算是否闰年
		//使用查表法记录每个月的天数
		int months[]={-1,31,28,31,30,31,30,31,31,30,31,30,31};//位置0不使用,设为-1
		if((year%4==0&&year%100!=0)||year%400==0)
			months[2]++;//如果该年为闰年,那么二月的天数加1
		int days=0;
		for(int i=1;i<month;i++)
		{
			days+=months[i];
		}
		days+=day;
		System.out.println(year+"年"+month+"月"+day+"日是该年的第"+days+"天!");
	}
	public static void main(String[] args) 
	{
		f(2015,6,23);
	}
}
运行图:

小结:使用查表法存放日期的作用非常聪明,使得程序即简单又易懂,而且效率高,因为对于数组来说,效率最高的使用就是按照下标索引元素了。

查表法总结:

综合来看,虽然查表法的使用范围不是非常的广,但是在能用到的场合,查表法的效率,代码可读性,代码简洁程序都非常好,相比if或者switch处理问题要更容易使用,且不容易出错。我们知道大量的if...else....语句是非常难以看懂的,而switch也存放一样的问题,因此我建议在能使用的地方使用查表法代替判断选择语句是非常明智的。

四、小练习

练习代码:
/**
选择排序
冒泡排序
折半查找
1.寻找key
2.插入key
*/
class TestAll
{
	public static void main(String[] args)
	{
		int[] ar={5,7,1,2,3,12,8};//静态初始化,不能指定长度
		System.out.println("排序前:");
		print(ar);
		sortChoose(ar);
		System.out.println("选择法排序后:");
		print(ar);

		int[] arr={12,2,34,4,57,78,9,0};
		System.out.println("排序前:");
		print(arr);
		sortBubble(arr);
		System.out.println("冒泡法排序后:");
		print(arr);

		System.out.println("折半查找元素78:");
		int index=halfSearch(arr,78);
		System.out.println("index:"+index);
		System.out.println("折半查找元素2:");
		int indexx=halfSearch(ar,2);
		System.out.println("index:"+indexx);

		System.out.println("插入元素3到ar中:");
		System.out.println("插入前:");
		print(ar);
		ar=halfInsert(ar,3);
		System.out.println("插入后:");
		print(ar);
		System.out.println("插入元素4到ar中:");
		System.out.println("插入前:");
		print(ar);
		ar=halfInsert(ar,4);
		System.out.println("插入后:");
		print(ar);
		System.out.println("插入元素-12到arr中:");
		System.out.println("插入前:");
		print(arr);
		arr=halfInsert(arr,-12);
		System.out.println("插入后:");
		print(arr);
		System.out.println("插入元素111到arr中:");
		System.out.println("插入前:");
		print(arr);
		arr=halfInsert(arr,111);
		System.out.println("插入后:");
		print(arr);

		System.out.println("函数重载:");
		int a=1,b=2,c=3;
		double d=1.1,e=2.2;
		System.out.println(add(a,b));
		System.out.println(add(a,b,c));
		System.out.println(add(d,e));
		System.out.println(add(d,b));
		System.out.println(add(a,e));

		System.out.println("int型数组arr的引用值:"+arr);
		byte[] array={1,3};
		System.out.println("byte型数组的引用值:"+array);
		short[] array3={2,4};
		System.out.println("short型数组的引用值:"+array3);
		long[] array6={12L,23L};
		System.out.println("long型数组的引用值:"+array6);
		double[] array1={1.2};
		System.out.println("double型数组的引用值:"+array1);
		float[] array2={1.1f};
		System.out.println("float型数组的引用值:"+array2);
		boolean[] array4={true,false};
		System.out.println("boolean型数组的引用值:"+array4);
		char[] array5={'a','b'};
		System.out.println("char型数组的引用值:"+array5);
	}
	private static void print(int[] arr)
	{
		for(int i=0;i<arr.length;i++)
		{
			System.out.print(arr[i]+" ");
		}
		System.out.println();
	}
	private static void sortChoose(int[] arr)
	{
		for(int i=0;i<arr.length-1;i++)
		{
			int k=i;
			for(int j=i+1;j<arr.length;j++)
			{
				if(arr[j]<arr[k])k=j;
			}
			if(k!=i)
			{arr[k]^=arr[i];arr[i]^=arr[k];arr[k]^=arr[i];}
		}
	}
	private static void sortBubble(int[] arr)
	{
		for(int i=0;i<arr.length-1;i++)
		{
			for(int j=0;j<arr.length-i-1;j++)
			{
				if(arr[j]>arr[j+1])
				{arr[j]^=arr[j+1];arr[j+1]^=arr[j];arr[j]^=arr[j+1];}
			}
		}
	}
	private static int halfSearch(int[] arr,int key)
	{
		for(int min=0,max=arr.length-1,mid=(min+max)/2;min<=max;mid=(min+max)/2)
		{
			if(arr[mid]==key)return mid;
			else if(arr[mid]>key)max=mid-1;
			else min=mid+1;
		}
		return -1;
	}
	private static int[] halfInsert(int[] arr,int key)
	{
		int[] ar=new int[arr.length+1];
		//1.找到key应该插入的位置(最左,最右,中间)
		//2.将key插入到该位置
		//3.将arr指向ar(指向新数组,数组arr实体被回收,ar被回收的是数组名的栈内存)
		int index=-1;
		for(int min=0,max=arr.length-1,mid=(min+max)/2;min<=max;mid=(min+max)/2)
		{
			if(arr[mid]==key){index=mid;break;}
			else if(arr[mid]>key)
			{
				if(mid==0){index=0;break;}
				else if(arr[mid-1]<=key){index=mid;break;}
				max=mid-1;
			}
			else min=mid+1;
		}
		if(index==-1)index=arr.length;
		for(int i=0;i<index;i++)
			ar[i]=arr[i];
		ar[index]=key;
		for(int i=index;i<arr.length;i++)
			ar[i+1]=arr[i];
		return ar;
	}
	private static int add(int a,int b)
	{return a+b;}
	private static int add(int a,int b,int c)
	{return a+b+c;}
	private static double add(double a,double b)
	{return a+b;}
	private static double add(int a,double b)
	{return a+b;}
	private static double add(double b,int a)
	{return a+b;}
}


运行图:



五、总结

不管是排序还是查找,或者是查表法,都是程序中最基本的算法,一定要牢牢掌握,虽然在Java中都已经被封装了,但是如果我们了解其内部实现,那么对于我们使用这种算法肯定是有百利而无一害的。


------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值