IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 排序算法讲解 -> 正文阅读

[数据结构与算法]排序算法讲解

1、冒泡排序

实现过程:假设有10个数,第一轮循环,第一个数和第二个数比较,如果第一个数大,第一个数和第二个数交换位置,否则不动;接着第二个数和第三个数比较,如果第二个数大,第二个数和第三个数交换位置, 否则不动……第九个数和第十个数比较,如果第九个数大,第九个数和第十个数交换位置,否则不动。第一轮循环结束,最大的数挪到了第十个数的位置,比较进行了9次。 第二轮循环,第一个数和第二个数比较,如果第一个数大,第一个数和第二个数交换位置,否则不动……第八个数和第九个数比较,如果第八个数大,第八个数和第九个数交换位置,否则不动。第二轮循环结束 ,第二大的数挪到了第九个数的位置,比较进行了8次。 …… 第九轮循环,第一个数和第二个数比较,如果第一个数大,第一个数和第二个数交换位置,否则不动。第九轮循环结束,倒数第二大的数挪到了第二个数的位置,比较进行了1次。 总体原理:每轮比较找到最大的数。 个人理解:分两次循环,外循环和内循环;外循环是n个数需要进行几次循环,内循环是每次外循环需要两两比较的次数; 每次外循环将最大或最小的数移至最右端,第二次循环时不再需要考虑最后一个数; 算法:外循环次数是:====n-1次 内循环的次数是:====n-i-1

时间复杂度:O(n2)

稳定性分析:冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

实现:

public int[]  bubble(int[] arr) {
		int tmp=0;
		for(int i = 0;i<arr.length-1;i++) {  //外循环,控制次数
			for(int j=0;j<arr.length-1-i;j++) { //内循环,两两比较
				if(arr[j+1]<arr[j]) {
					tmp=arr[j+1];
					arr[j+1]=arr[j];
					arr[j]=tmp;
				}
			}
		}
		return arr;
	}

2、选择排序

实现过程:在数组的前提下,每次外循环遍历最小带的值(或最大的值)存到前面,后一次循环,从存好的最大(或最小的值)后一位开始遍历再找到 最大(或最小值)存到前面;n个数,外循环需要n-1次; 个人理解:定义一个中间变量,min存放最小值,且初始值都默认是开始遍历的第一位,用index记录最小值的下标,每次内循环找到最小值存到min,下标存到index,然后和开始遍历的第一个元素更换;

时间复杂度:O(n2)

稳定性分析:选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

public int[] option(int arr[]) {
		int xiabiao=0;  //存放最大值的下标,方便交换
		int tmp=0;
		for(int i=0;i<arr.length-1;i++) {
			int max = arr[i];
			for(int j=1+i;j<arr.length;j++) {
				if(arr[j]>max) {
					max = arr[j];
					xiabiao = j;
				}
			}
			if(arr[i]!=max) {
				tmp=arr[xiabiao];
				arr[xiabiao]=arr[i];
				arr[i]=tmp;
			}
		}
		return arr;
	}

3、插入排序

实现过程:在数组的前提下,也分为外循环和内循环,从第二个数开始比较,与前面的每一个数比,如果小于 前面的数,则插入到前面的数前面,否则退出循环 个人理解:从第二个数开始比较,与前面的数比,大于则退出循环,小于则插入前面的数前面,以此类推

时间复杂度:O(n2)

稳定性分析:如果待排序的序列中存在两个或两个以上具有相同关键词的数据,排序后这些数据的相对次序保持不变,即它们的位置保持不变,通俗地讲,就是两个相同的数的相对顺序不会发生改变,则该算法是稳定的;如果排序后,数据的相对次序发生了变化,则该算法是不稳定的。关键词相同的数据元素将保持原有位置不变,所以该算法是稳定的

public int[] insert(int arr[]) {
		int tmp =0;
		for(int i =1;i<arr.length;i++) {
			for(int j=i-1;j>=0;j--) {
				if(arr	[i]>arr[j]) {//这里交换的是i位置和j位置的值!i指向的位置不变!
					tmp= arr[i];
					arr[i]=arr[j];
					arr[j]=tmp;
					i--;  //注意这里!交换完以后i需要向前移一位
				}
			}
		}
		return arr;
	}

4、希尔排序

实现过程:它分为三个循环,最外层是控制增量的,后两个其实就是插入排序,i代表第二层for循环,j代表第三层for循环,i的话是数组下标每次向右增加一个增量(也就是小组成员加一了,这时,这个 新增的成员要分别与本小组的其他成员比较,这就用到了变量j),增量公式:n/2 个人理解:它是插入排序的变种(优化),插入排序是将前面部分看成一个整体数组,后面遍历的数分别与这个数组的每个值比较,而希尔排序是将数据按照增量分为了几个小组,每个小组内部实现 插入排序

时间复杂度:O(n^(1.3)) -O(n^(2)) 之间,反正是小于O(n2)

稳定性分析:由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

public static void shell(int arr[]) {
		int tmp = 0;
		 //增量gap,并逐步缩小增量
		        for(int gap=arr.length/2;gap>0;gap/=2){
		            //从第gap个元素,逐个对其所在组进行直接插入排序操作
		            for(int i=gap;i<arr.length;i++){
		                int j = i;
		                while(j-gap>=0 && arr[j]<arr[j-gap]){  //注意,这里用while最好,不要用for
		                    //插入排序采用交换法
		                    tmp = arr[j];
		                    arr[j]=arr[j-gap];
		                    arr[j-gap]=tmp;
		                    j-=gap;  //注意这个细节!交换完后j需要前挪
		                }
		            }
		        }
	}

5、快速排序

链接:https://blog.csdn.net/nrsc272420199/article/details/82587933

快速排序,说白了就是给基准数据找其正确索引位置的过程.

设数组为: 23、46、0、8、11、18

假设最开始的基准数据为数组第一个元素23,则首先用一个临时变量去存储基准数据,即tmp=23;然后分别从数组的两端扫描数组,设两个指示标志:low指向起始位置,high指向末尾. 用一个临时变量存储基准数据23 tmp=23;

首先从后半部分开始,如果扫描到的值大于基准数据就让high减1,如果发现有元素比该基准数据的值小(如上图中18<=tmp),就将high位置的值赋值给low位置

结果为:18、46、0、8、11、18

然后开始从前往后扫描,如果扫描到的值小于基准数据就让low加1,如果发现有元素大于基准数据的值(如上图46=>tmp),就再将low位置的值赋值给high位置的值,

结果为:18、46、0、8、11、46

最终23的位置为:4

也就是:18、11、0、8、23、46

后续操作采用递归

时间复杂度:O(nlogn)

稳定性分析:不稳定。基准数字的换位时发生

package sort;

public class quickSort {
	public static void quick(int [] arr,int low,int high) {
		if(low<high) {  //递归的终结条件,当low=high时,也就是只有一个数,不用比较交换
			int index=getIndex(arr,low,high);
			quick(arr,low,index-1);  
			quick(arr,index+1,high);
		}
	}
	
	public static int getIndex(int[] arr,int low,int high) {
		int tmp = arr[low];  //使用tmp存储基准数据(一般是首位的数字)
		while(low<high) {  //条件为low<high,low=high的时候也就找到了下标位置
			while(low<high&&arr[high]>=tmp) {  //因为有++和--操作,所以,需要判断low<high
				high--;
			}
			arr[low]=arr[high];
			while(low<high&&arr[low]<=tmp) {
				low++;
			}
			arr[high]=arr[low];
		}
		arr[low]=tmp; //这一步,也就是low=high,找到了下标位置
		return low;  //找到基准数据的下标位置返回
	}
	
	public static void main(String[] args) {
		int[] arr = { 23,46,0,8,11,18 };
		quick(arr, 0, arr.length - 1);
		System.out.println("排序后:");
		for (int i : arr) {
			System.out.println(i);
		}
	}
}

6、归并排序

链接:https://www.cnblogs.com/chengxiao/p/6194356.html

实现过程:给定一个无序数组,然后写一个递归方法和一个合并方法,递归负责将无序数组分解为单个的数字,在回溯阶段,调用合并方法 个人理解:利用了分治思想,分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之 一堆数据,一直拆分,直至分为一个一个的数,然后将单个数字合并为两个数字(有序的),再两两合并为四个数字(有序的),一直这样下去,直到又组合成 一行数,至此,排序完成。 合并是利用一个临时数组,两个指针,这两个指针开始默认指向两个即将合并的数组的第一位,分别比较,最后合并到临时数组中去 在代码实现中,可以用递归回溯的方式,先拆后合。

时间复杂度:O(nlogn)

稳定性分析:因为交换元素时,可以在相等的情况下做出不移动的限制,所以归并排序是可以稳定的

public class mergeSort {
	
	public static void sort(int []arr) {
		int temp[] = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
		sort(arr,0,arr.length-1,temp);
	}
	
	public static void sort(int[] arr,int left,int right,int [] temp) {
		if(left<right) {
			int mid =(left+right)/2;
			sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
			sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
			merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
		}
	}
	
	public static void merge(int[]arr,int left,int mid,int right,int[]temp) {
		int i=left;//左序列指针
		int j=mid+1;//右序列指针
		int t=0;//临时数组指针
		while(i<=mid&&j<=right) {
			if(arr[i]<=arr[j]) temp[t++]=arr[i++];
			else temp[t++]=arr[j++];
		}
		while(i<=mid) temp[t++]=arr[i++];//将左边剩余元素填充进temp中
		while(j<=right) temp[t++]=arr[j++];//将右序列剩余元素填充进temp中
		t=0;
		while(left<=right) arr[left++]=temp[t++]; //将temp中的元素全部拷贝到原数组中
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int []arr = {9,8,7,6,5,4,3,2,1};
        sort(arr);
        System.out.println(Arrays.toString(arr));
	}

}

7、堆排序

堆的概念:

每个结点的值都大于或等于其左右孩子结点的值的完全二叉树,称为大顶堆;

每个结点的值都小于或等于其左右孩子结点的值的完全二叉树,称为小顶堆;

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序

可以用数组来表示堆:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

思路:

利用堆进行排序,将待排的序列构成个大顶堆,最大值就是根节点;将根节点移走,(将其和堆末尾元素交换,此时末尾 元素就是最大值),接着将剩余的n-1个序列重新构成一个堆,继续进行。

个人理解:第一步:创建一个大顶或小顶堆 第二步:依次将堆顶元素和尾元素交换,然后重新将剩余的元素构建堆

时间复杂度:它的最坏,最好,平均时间复杂度均为O(nlogn)

稳定性分析:不稳定

public class heapSort {
	 public static void main(String []args){
	        int []arr = {9,8,7,6,5,4,3,2,1};
	        sort(arr);
	        System.out.println(Arrays.toString(arr));
	    }
	    public static void sort(int []arr){
	        //1.构建大顶堆
            //这里int i=arr.length/2-1是求出堆的左下角的第一个非叶子节点的下标
	        for(int i=arr.length/2-1;i>=0;i--){
	            //从第一个非叶子结点从下至上,从右至左调整结构
	            adjustHeap(arr,i,arr.length);
	        }
	        //2.调整堆结构+交换堆顶元素与末尾元素
            //这里的j--就是剩下的元素,排好的不管了,有点像冒泡
	        for(int j=arr.length-1;j>0;j--){
	            swap(arr,0,j);//将堆顶元素与末尾元素进行交换
	            adjustHeap(arr,0,j);//重新对堆进行调整
	        }
	    }

	    /**
	     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
	     * @param arr
	     * @param i
	     * @param length
	     */
    //这个方法就是找最大或最小放根节点
	    public static void adjustHeap(int []arr,int i,int length){
	        int temp = arr[i];//先取出当前元素i
	        for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
                //如果存在右节点,比较左节点和右节点,找到最大值
	            if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
	                k++;
	            }
                //将找到的最大值和根节点比较,也是找到最大值
	            if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
	                arr[i] = arr[k];
	                i = k;
	            }else{
	                break;
	            }
	        }
	        arr[i] = temp;//将temp值放到最终的位置
	    }

	    /**
	     * 交换元素
	     * @param arr
	     * @param a
	     * @param b
	     */
	    public static void swap(int []arr,int a ,int b){
	        int temp=arr[a];
	        arr[a] = arr[b];
	        arr[b] = temp;
	    }

}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-10-22 11:11:28  更:2021-10-22 11:13:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:43:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码