1. 程式人生 > >快排三種基本解法以及兩種快排優化

快排三種基本解法以及兩種快排優化

/*
 快速排序
 基本思想
   選定每次排序的基準資料 在剩下的位置將小於基準值的資料放在基準值得左邊,大於基準值的資料放到基準值的右邊
   一次劃分之後 如果此基準值的左右兩邊仍存在大於兩個資料  則繼續劃分排序 直至每個數字都有序
  遞迴實現Quick_Sort1(int *arr,int len):快排一次劃分的時間複雜度為O(logn) 最壞就是在有序條件下 時間複雜度為O(n^2)
  整體時間複雜度為O(n*logn)
  非遞迴實現Quick_Sort2(int *arr,int len)

 快排三種實現方式
   第一種就是選取第一個元素或者最後一個元素作為基準 程式碼是Quick_Sort1 和Quick_Sort2
   這樣的壞處是如果所給序列是有許多的  那麼時間複雜度太大 選取的基準點就失去了意義 並不能稱作是基準值了
   第二種隨機取值法
    絕大多數情況下保證了時間複雜度最好情況是O(logn)  但是最壞情況下 假如整個陣列的數字都相等 那麼時間複雜度還是能達到O(n^2)
   第三種 三數取中
   找到low high  mid = low + ((high - low) >> 1);//計算陣列中間的元素的下標    

取這三個下標對應的最小值 保證arr[low]是這三個數之間的第二大值 然後又可以和普通劃分函式一樣

*/

void swap(int *a,int *b)
{
	int tmp  = *a;
	*a = *b;
	*b = tmp;
}
//第一種劃分函式:以第一個數字為基準值進行一次劃分 最後返回基準的下標
int partion(int *arr,int low,int high)
{
	if(arr == NULL )
		return -1;
	int tmp = arr[low];

	while(low < high)
	{
		while((low < high) && arr[high] >= tmp)
		{
			high--;
		}
		if(low == high)//如果遇到比基準值小的 放到low的位置
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
		}

		while((low < high) && arr[low] <=  tmp)
		{
			low++;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}
//第二種劃分函式:隨機選取基準劃分
int part(int *arr,int low,int high)
{
	srand((unsigned)time(NULL));
	//產生隨機位置
    int pivotPos = rand()%(high - low) + low;
	//將此隨機位置的值與low位置的值交換 又可以和普通劃分函式一樣
	swap(arr[pivotPos],arr[low]);

	int tmp = arr[low];

	while(low < high)
	{
		while((low < high) && arr[high] >= tmp )
		{
			high--;
		}
		if(low == high)//如果遇到比基準值小的 放到low的位置
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
		}

		while((low < high) && arr[low] <=  tmp)
		{
			low++;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}
//第三種劃分函式
int Select_Mid(int *arr,int low,int high)
{
	//int mid = low + ((high - low) >> 1);//計算陣列中間的元素的下標  
	int mid = low + ((high - low) >> 1);
    //使用三數取中法選擇樞軸  
    if (arr[mid] > arr[high])//目標: arr[mid] <= arr[high]  
    {  
        swap(arr[mid],arr[high]);  
    }  
    if (arr[low] > arr[high])//目標: arr[low] <= arr[high]  
    {  
        swap(arr[low],arr[high]);  
    }  
    if (arr[mid] > arr[low]) //目標: arr[low] >= arr[mid]  
    {  
        swap(arr[mid],arr[low]);  
    }  
	return arr[low];
}
//三數取中的一次劃分
int part1(int *arr,int low,int high)
{
	
	int tmp = Select_Mid(arr,low,high);

	while(low < high)
	{
		while((low < high) && arr[high] >= tmp )
		{
			high--;
		}
		if(low == high)//如果遇到比基準值小的 放到low的位置
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
		}

		while((low < high) && arr[low] <=  tmp)
		{
			low++;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}
static void Quick(int *arr,int start,int end)
{
	int par = part1(arr,start,end);
	if(par > start + 1)
	{
		Quick(arr,start,par-1);
	}
	if(par < end -1)
	{
		Quick(arr,par+1,end);
	}
}
void Quick_Sort1(int *arr,int len)
{
	if(arr == NULL ||len < 0)
		return;
	for(int i = 0 ;i < len - 1 ;++i)
	{
		Quick(arr,0,len-1);
	}
}

快排非遞迴實現

//用棧儲存每一次的一對low和high區間 空間複雜度為O(n*logn)
void Quick_Sort2(int *arr,int len)
{
	if( arr == NULL ||len < 0)
		return ;
	int low = 0;
	int high = len - 1;
	int par =  partion(arr,low,high);
	stack<int> st;
	if(par > low + 1)
	{
		st.push(low);
		st.push(par-1);
	}
	if(par < high - 1)
	{
		st.push(par+1);
		st.push(high);
	}
	while(!st.empty())
	{
		int high = st.top();st.pop();
		int low = st.top();st.pop();
		int par = partion(arr,low,high);
		if(par > low + 1)
		{
			st.push(low);
			st.push(par-1);
	}
	if(par < high - 1)
	{
		st.push(par+1);
		st.push(high);
	}
	}
}

/*

快排優化一

在low和high之間的舉例為10以內時,可以選擇直接插入排序 節省時間

*/

//快排優化一 在劃分到適合的長度進行直接插入排序
void insert_sort(int *arr,int low,int high)
{
	if(arr == NULL || low < 0 || high < 0)
		return ;
	int tmp = 0;
	int j;
	for(int i = low+1;i < high; i++)
	{
		tmp = arr[i];
		for(j = i-1;j >= 0;j--)
		{
			if(arr[j] > tmp)
			{
				arr[j+1] = arr[j];
			}
			else
			{
				break;
			}
		}
		arr[j+1] = tmp;
	}
}
static void Quick1(int *arr,int start,int end)
{
	int par = part1(arr,start,end);
	if(end - start + 1 < 10)
	{
			insert_sort(arr,start,end);
	}
	else
	{
		if(par > start + 1)
		{
			Quick(arr,start,par-1);
		}
		if(par < end -1)
		{
			Quick(arr,par+1,end);
		}
	}
}
void Quick_Sort3(int *arr,int len)
{
	if(arr == NULL ||len < 0)
		return;
	for(int i = 0 ;i < len - 1 ;++i)
	{
		Quick(arr,0,len-1);
	}
}

/*

 快排優化二
   在一次劃分之後 將與基準值相等的元素聚集在一起不再進行下一次的劃分操作 可以減少迭代查詢次數
   具體操作將與基準值相等的數放到陣列的兩端 一次劃分完成後 將這些數字挪回基準值得範圍 具體過程如下圖所示

*/

//快排優化二 一次劃分後將基準值調至靠近基準值周圍 與基準值相等的數字不再參加下一次劃分
void QuickSort(int *arr,int low,int high)
{
	int first = low;
    int last = high;

    int left = low;
    int right = high;

    int leftLen = 0;
    int rightLen = 0;


    //一次分割
    int key = Select_Mid(arr,low,high);//使用三數取中法選擇樞軸

    while(low < high)
    {
        while(high > low && arr[high] >= key)
        {
            if (arr[high] == key)//處理相等元素
            {
                swap(&arr[right],&arr[high]);
                right--;
                rightLen++;
            }
            high--;
        }
        arr[low] = arr[high];

        while(high > low && arr[low] <= key)
        {
            if (arr[low] == key)
            {
                swap(&arr[left],&arr[low]);
                left++;
                leftLen++;
            }
            low++;
        }
        arr[high] = arr[low];
    }
    arr[low] = key;

    //一次快排結束
    //把與樞軸key相同的元素移到樞軸最終位置周圍
    int i = low - 1;
    int j = first;
    while(j < left && arr[i] != key)
    {
        swap(&arr[i],&arr[j]);
        i--;
        j++;
    }
    i = low + 1;
    j = last;
    while(j > right && arr[i] != key)
    {
        swap(&arr[i],&arr[j]);
        i++;
        j--;
    }
	//如果左右兩端都還有超過兩個資料 那麼就需要進行劃分排序
	if(low-1 > first+1)
	{
		QuickSort(arr,first,low - 1 - leftLen);
	}
	if(low+1 < last-1)
	{
		QuickSort(arr,low + 1 + rightLen,last);
	}

}

相關推薦

基本解法以及優化

/*  快速排序  基本思想    選定每次排序的基準資料 在剩下的位置將小於基準值的資料放在基準值得左邊,大於基準值的資料放到基準值的右邊    一次劃分之後 如果此基準值的左右兩邊仍存在大於兩個資料  則繼續劃分排序 直至每個數字都有序   遞迴實現Quick_Sort1

leetcode threeSumMulti 的python解法(後特別好)

上週末參加了leetcode的考試, 做出兩道題。不得不接受自己的愚笨。 第三題在看了排名前兩名java的答案和另一個python做的答案之後,除了感嘆人家演算法的精妙,也只能暗下決心,要花更多的時間來刷題! https://leetcode.com/contest/weekly-conte

javaWeb基礎之Servlet的實現方式以及配置方式

一、Servlet的三種實現方式 Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務程式或服務聯結器,用Java編寫的伺服器端程式,主要功能在於互動式地瀏覽和修改資料,生成動態Web內容。 1、Servlet的第一種建立方式:繼承Ht

圖的基本概念表示方法以及搜尋方式——深度優先遍歷和廣度優先遍歷

原先的知識沒好好學,導致現在很多都忘了,或者說以前就沒弄懂過。現在重新看一遍,收穫良多,不管怎樣先寫這篇基礎的,當做筆記。 圖的定義:是由頂點的有窮非空集合和頂點之間的邊的集合組成的,通常表示為 G(V,E)。其中G表示一個圖,V是圖的頂點的集合,E是圖的邊的集合。 有跟多

【漏洞預警】CVE-2017-8464 震網代漏洞復現(利用方法)

art cal mage http test ip地址 get for oot 早在6月13日,微軟發布補丁修復編號為CVE-2017-8464的漏洞,本地用戶或遠程攻擊者可以利用該漏洞生成特制的快捷方式,並通過可移動設備或者遠程共享的方式導致遠程代碼執行,追溯到以前,NS

log4j2筆記 #04# Appender的基本以及RollingFile的各種示例配置

粗糙筆記,留著備用。 三個基本款分別是ConsoleAppender、FileAppender(以及他的堂哥RandomAccessFileAppender)、RollingFileAppender(以及他的堂哥llingRandomAccessFileAppender),其中RollingFileAppe

java多執行緒(一):執行緒的五基本狀態以及生命週期

1、Java執行緒具有五中基本狀態: 新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread thread1 = new MyThread(); 就緒狀態(Runnable):當呼叫執行緒物件的start()方法[ 如:thread1 .start(); ],執行緒

面向物件的基本特徵和五設計原則

面向物件的三個基本特徵 抽象與封裝: 抽象是把系統中需要處理的資料和在這些資料上的操作結合在一起,根據功能、性質和用途等因素抽象成不同的抽象資料型別。每個抽象資料型別既包含了資料,又包含了針對這些資料的授權操作。在面向物件的程式設計中,抽象資料型別是用“類”

redis的key的通用操作,特性,以及持久化

在網上學了redis的入門後,將學習的剩下內容整理如下: 1.關於key的通用操作 1.查詢資料庫裡的keys列表集合 keys * 舉個栗子:看我到現在的有多少個物件存在   2.查詢資料庫裡的有關鍵詞的物件 keys *keyword* #關鍵

Spring 詳解(二)------- AOP關鍵概念以及實現方式

目錄 1. AOP 關鍵詞 2. AOP 的作用 3. AOP 的通知型別 4. 基於 xml 的配置方式 5. 基於註解的配置方式 6. 切面的優先順序 7. 重用切點表示式 8. 兩種方式的比較(摘自 spring 官方文件) 1. AOP 關鍵詞

實現多執行緒的方式以及方式之間的區別

Java中有兩種實現多執行緒的方式。一是直接繼承Thread類,二是實現Runnable介面。那麼這兩種實現多執行緒的方式在應用上有什麼區別呢?        為了回答這個問題,我們可以通過編寫一段程式碼來進行分析。我們用程式碼來模擬鐵路售票系統,實現通過四個售票點發售某日

Spring 詳解(二)------- AOP概念以及實現方式

  target:目標類,需要被代理的類。例如:ArithmeticCalculator      Joinpoint(連線點):所謂連線點是指那些可能被攔截到的方法。例如:所有的方法      PointCut 切入點:已經被增強的連線點。例如:add()      advice:通知/增強,增強

XP遠端桌面中mstsc和的mstsc /console的差別,以及方式實習遠端關機!

 Mstsc.exe is the Remote Desktop Connection tool which was the Terminal Services client in earlier versions of Windows. you can use the /c

算法系列()插入排序的改進:規避邊界檢測和取消交換(Java實現)

前言:演算法第四版習題2.1.24插入排序的哨兵和習題2.1.25不需要交換的插入排序 規避邊界檢測: 在插入排序的實現中先找到最小的元素並將其置於陣列的第一個位置,可以省掉內迴圈的判斷條件 j>0 。能夠省略判斷條件的元素稱為哨兵。 public class Ex

Linux下chkconfig命令詳解即新增服務以及方式啟動關閉系統服務

chkconfig命令主要用來更新(啟動或停止)和查詢系統服務的執行級資訊。謹記chkconfig不是立即自動禁止或啟用一個服務,它只是簡單的改變了符號連線。 一、chkconfig 的使用語法 1、chkconfig [--add][--del][--list][系統

內部排序()堆排序的實現

堆排序是一種選擇排序演算法,堆排序顧名思義要用到堆,首先來回顧下有關資料結構“堆”有哪些特點。 堆常用二叉樹來表示,而且如果不是特殊情況的話,通常用一棵完全二叉樹來表示堆。因為完全二叉樹的結點分佈均勻,所以通常可以用陣列來實現堆的儲存。 根據堆中任一結點和其他結點的值的關

面向物件的基本特徵 和 五設計原則

一、三個基本特徵 面向物件的三個基本特徵是:封裝、繼承、多型。 封裝 封裝最好理解了。封裝是面向物件的特徵之一,是物件和類概念的主要特性。 封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。 繼承 面向物件程式設計 (OOP) 語言的一個

Redis的持久化機制包括RBD和AOF,對於這持久化方式各有優勢

plain 同步數據 pen toc 默認 ocl 好的 dfs 操作系統 RDB機制的策略 RDB持久化是指在指定的時間間隔內將內存中的數據和操作通過快照的方式保存到redis bin目錄下的一個默認名為 dump.rdb的文件,可以通過配置設置自動的快照持久化的

Http協議中,主要常見的傳送資料到伺服器有哪方式,這方式的特點和區別,以及其在Http協議中的位置

Get 和 Post 的區別兩點: 一、這兩者傳遞引數時所用的編碼不一定是一樣的。在 Tomcat 中似乎 Get 的編碼方式是根據頁面中指定的編碼方式,而 Post 則是一直使用同一種編碼方式,可在 Tomcat 的 server.xml 中配置。 二、使用 Get 的時候,引數會顯示在位址列上,而 Po

Java 的8基本型別和3引用型別

1、計算機的記憶體單位       計算機中的資料都是以 0 和 1 來表示的,其中一個 0 或者一個 1 稱之為一位 (bit)。       8位稱為一個位元組 (Byte),兩個位元組稱為一個字 (Word) ,四個位元組稱為雙字 (Dword)。      1Byt