国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分)

這篇具有很好參考價值的文章主要介紹了【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)

???內(nèi)容專欄: 《數(shù)據(jù)結(jié)構(gòu)與算法篇》
??本文概括: 利用數(shù)據(jù)結(jié)構(gòu)棧(Stack)來模擬遞歸,實現(xiàn)快排的非遞歸版本;遞歸版本測試OJ題時,有大量重復(fù)元素樣例不能通過,導(dǎo)致性能下降,優(yōu)化快速排序通過將數(shù)組劃分為三個區(qū)域,可以更有效地處理重復(fù)元素。
??本文作者: 阿四啊
??發(fā)布時間:2023.8.28

快速排序(非遞歸)

1.為什么要學(xué)習(xí)非遞歸版本?

前面我們使用了三個版本實現(xiàn)快速排序,但都是屬于遞歸類型算法,函數(shù)調(diào)用會建立函數(shù)棧幀,遞歸深度取決于函數(shù)調(diào)用的次數(shù)。棧空間內(nèi)存有限,在處理大規(guī)模數(shù)據(jù)集時,遞歸深度的增加可能導(dǎo)致棧溢出的情況,所以在我們學(xué)會了遞歸版本之后,需要繼續(xù)強(qiáng)化學(xué)習(xí)非遞歸版本,利用之前學(xué)習(xí)的數(shù)據(jù)結(jié)構(gòu)棧(Stack)來模擬實現(xiàn)。

2.基本思想

我們知道,我們在寫遞歸版本的時候,快速排序主要處理的是數(shù)組的不同區(qū)間,將問題分解為較小的子問題并在每個子問題上遞歸地應(yīng)用相同的排序算法來完成排序。

【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)
那么我們?nèi)绾问褂脳?Stack)來模擬實現(xiàn)呢?

核心思想是使用一個棧來存儲待排序的子數(shù)組的起始和結(jié)束下標(biāo)。在每次循環(huán)中,從棧中彈出一個子數(shù)組并執(zhí)行分區(qū)操作(根據(jù)基準(zhǔn)值(key),劃分區(qū)間,這一步驟即遞歸版本寫的Partition函數(shù)),選然后根據(jù)分區(qū)結(jié)果將未處理的左右子數(shù)組的下標(biāo)壓入棧中,直到棧為空為止。

3.算法分析

??①:
【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)
??②:
【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)
??③:繼續(xù)重復(fù)以上步驟,直到棧中的數(shù)據(jù)為空。

4.代碼實現(xiàn)

因為涉及到使用棧,而本篇數(shù)據(jù)結(jié)構(gòu)基于C語言講解,所以講自己實現(xiàn)的棧的文件源代碼也順便放在這。

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//hoare版本
int Partition1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		//注意: 要加上left < right 否則會出現(xiàn)越界
		//若不判斷等于基準(zhǔn)值,也會出現(xiàn)死循環(huán)的情況
		//右邊找小
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}

		//左邊找大
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);

	return left;
}

void QuicksortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);

	//注意棧的順序是后進(jìn)先出,需要倒著放進(jìn)去,正著拿出來
	
	//將排序數(shù)組的起始和末端下標(biāo)入棧
	StackPush(&st, end);
	StackPush(&st, begin);

	//棧不為空一直循環(huán)
	while (!StackEmpty(&st))
	{
		//彈出子區(qū)間(左右兩個下標(biāo))
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);

		//執(zhí)行分區(qū)操作
		int keyi = Partition1(a, left, right);
		//[left,keyi - 1] keyi [keyi + 1, right]

		//注意只剩一個數(shù)或區(qū)間不存在則停止入棧
		if (keyi + 1 < right)
		{
			StackPush(&st,right);
			StackPush(&st,keyi + 1);
		}

		if (left < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}

}
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
}
int main()
{
	int a[] = { 9,6,7,1,4,5,5,2,10,1,6,3,7 };
	QuicksortNonR(a, 0, sizeof a / sizeof(int) - 1);
	PrintArray(a, sizeof a / sizeof(int));
}


相關(guān)函數(shù)聲明:

#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
typedef int DataType;
typedef struct Stack
{
	DataType* a;
	int top;
	int capacity;

}Stack;

//棧的初始化
void StackInit(Stack* pst);
//入棧
void StackPush(Stack* pst,DataType x);
//出棧
void StackPop(Stack* pst);
//棧的銷毀
bool StackEmpty(Stack* pst);
//獲取棧頂元素
DataType StackTop(Stack* pst);
//獲取棧元素個數(shù)
int StackSize(Stack* pst);
//棧的銷毀
void StackDestroy(Stack* pst);

相關(guān)函數(shù)實現(xiàn):

#define  _CRT_SECURE_NO_WARNINGS 
#include "stack.h"
//棧的初始化
void StackInit(Stack* pst)
{
	assert(pst);
	pst->a = NULL;
	//pst->top = -1;//棧頂元素的位置
	pst->top = 0;//棧頂元素的下一個位置
	pst->capacity = 0;
}
//入棧
void StackPush(Stack* pst,DataType x)
{
	if (pst->top == pst->capacity)
	{
		int newCapacity = pst->capacity == 0 ? 4 : (pst->capacity) * 2;
		DataType* tmp = (DataType*)realloc(pst->a, sizeof(DataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			return;
		}
		else
		{
			pst->a = tmp;
			pst->capacity = newCapacity;
		}
	}
	pst->a[pst->top++] = x;
}
//出棧
void StackPop(Stack* pst)
{
	assert(pst);
	pst->top--;
}
//判斷棧是否為空
bool StackEmpty(Stack* pst)
{
	assert(pst);
	return pst->top == 0;
}
//獲取棧頂元素
DataType StackTop(Stack* pst)
{
	return pst->a[pst->top - 1];
}
//獲取棧元素個數(shù)
int StackSize(Stack* pst)
{
	return pst->top;
}
//棧的銷毀
void StackDestroy(Stack* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

總結(jié)

時間復(fù)雜度:O(N*logN)
空間復(fù)雜度:O(logN)
??注意:盡管使用棧實現(xiàn)了遞歸過程,但棧的使用本質(zhì)上是在模擬遞歸調(diào)用棧。這種方法可以避免遞歸深度過大導(dǎo)致棧溢出的問題,并且在一些情況下比遞歸版本更有效。

遞歸版本優(yōu)化(三路劃分)

1.缺陷問題:

下面給出一道力扣OJ題:??傳送鏈接:912.排序數(shù)組
題目要求就是給定一些數(shù)據(jù),將亂序的數(shù)組排列為升序。我們用希爾排序、歸并排序、堆排序……一般效率較高的都可以跑過,但是唯獨快排用遞歸、非遞歸都不能跑過!就很離譜,因為快排有名,也就是這題故意針對快排,下面利用寫出的遞歸版本的快排去跑這個OJ。

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三數(shù)取中
int GetMidIndax(int* a,int left, int right)
{
		int mid = left + (rand() % (right - left));

		if (a[mid] > a[left])
		{
			if (a[right] > a[mid])	return mid;
			else if (a[right] > a[left]) return right;
			else return left;
		}
		else //a[mid] < a[left]
		{
			if (a[right] < a[mid]) return mid;
			else if (a[right] < a[left]) return right;
			else return left;
		}

}

 //hoare版本
int Partition1(int* a, int left, int right)
{
	  int midi = GetMidIndax(a, left, right);
		Swap(&a[left],&a[midi]);
		int keyi = left;
		while (left < right)
		{
			//右邊找小
			while (left < right && a[right] >= a[keyi])
			{
				right--;
			}

			//左邊找大
			while (left < right && a[left] <= a[keyi])
			{
				left++;
			}

			Swap(&a[left], &a[right]);
		}
		Swap(&a[left], &a[keyi]);

	return left;
}
 //快排(遞歸版本)
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end) return;

	int keyi = Partition1(a, begin, end);
 
	//前序遍歷
	//遞歸基準(zhǔn)值左邊的區(qū)間
	QuickSort(a, begin, keyi - 1);
	//遞歸基準(zhǔn)值右邊的區(qū)間
	QuickSort(a, keyi + 1, end);
}

int* sortArray(int* nums, int numsSize, int* returnSize){
    QuickSort(nums, 0, numsSize - 1);
    *returnSize = numsSize;

    return nums;
}

我們發(fā)現(xiàn)這題一共21個測試用例,只通過了17個,顯示超出時間限制,并且nums里出現(xiàn)了很多2,因為有大量重復(fù)元素樣例不能通過,這樣的基準(zhǔn)值key一直是處于數(shù)組第一個位置,導(dǎo)致性能下降,出現(xiàn)了最壞的情況,時間復(fù)雜度為O(N2)
【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)

2.解決方案:

  • [----------------------------------------------------------------------------------------------------------------------------------------]
    【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分),數(shù)據(jù)結(jié)構(gòu)與算法,排序算法,算法,數(shù)據(jù)結(jié)構(gòu)

基本思想:三路劃分通過將數(shù)組劃分為三個部分來解決這個問題:小于、等于和大于基準(zhǔn)值的元素。文章來源地址http://www.zghlxwxcb.cn/news/detail-679060.html

  • 用隨機(jī)值三數(shù)取中法選擇一個基準(zhǔn)值key 。
  • 定義三個指針變量:left、cur、right,left的初始值為區(qū)間的起始位置,cur指針的初始值為left + 1,right指針的初始值為區(qū)間的末尾位置。
  • 遍歷數(shù)組,通過與基準(zhǔn)值的比較將元素分成三部分:
    1. 如果當(dāng)前元素小于key ,將它與left指針?biāo)傅奈恢媒粨Q,然后將left指針和cur指針都向右移動。
    2. 如果當(dāng)前元素等于key ,將cur指針向右移動。
    3. 如果當(dāng)前元素大于key ,將它與right指針?biāo)傅奈恢媒粨Q,然后將right指針向左移動。
  • left指針和right指針之間的區(qū)域(包含leftright)即為與key相等的所有元素,然后前序遍歷,對left指針左邊區(qū)域進(jìn)行遞歸,right指針右邊區(qū)域進(jìn)行遞歸。
  • 最終,數(shù)組會被劃分為三個區(qū)域:小于key區(qū)域 、等于key區(qū)域 和大于key區(qū)域。

3.代碼參考

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int GetMidIndax(int* a,int left, int right)
{
	int mid = left + (rand() % (right - left));
	if (a[mid] > a[left])
	{
		if (a[right] > a[mid])	return mid;
		else if (a[right] > a[left]) return right;
		else return left;
	}
	else //a[mid] < a[left]
	{
		if (a[right] < a[mid]) return mid;
		else if (a[right] < a[left]) return right;
		else return left;
	}

}
//三路分割 左 l r 右
void QuickSort(int* a, int begin, int end)
{
    
	if (begin >= end) return;

	int left = begin;
	int right = end;
	int cur = left + 1;

    int mid = GetMidIndax(a, begin, end);
    Swap(&a[mid],&a[left]);

	int key = a[left];

	while (cur <= right)
	{
		if (a[cur] < key)
		{
			Swap(&a[cur], &a[left]);
			left++;
			cur++;
		}
		else if (a[cur] > key)
		{
			Swap(&a[cur], &a[right]);
			right--;
		}
		else //a[cur] == key
		{
			cur++;
		}
	}

	// [begin left- 1],[left,right],[right + 1,end]
	QuickSort(a, begin, left - 1);
	QuickSort(a, right + 1, end);
}
 
int* sortArray(int* nums, int numsSize, int* returnSize){
    srand(time(NULL));
    QuickSort(nums, 0, numsSize - 1);
    *returnSize = numsSize;

    return nums;
}

到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕八大排序算法之快排的非遞歸實現(xiàn)及遞歸版本優(yōu)化(三路劃分)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu)之八大排序算法

    數(shù)據(jù)結(jié)構(gòu)之八大排序算法

    排序:所謂排序,就是使一串記錄,按照其中的某個或某些的大小,遞增或遞減的排列起來的操作。 下面是常見的排序算法: 直接插入排序是一種簡單的插入排序法,其基本思想是: 把待排序的元素按其值的大小逐個插入到一個已經(jīng)排好序的有序序列中,直到所有的

    2023年04月14日
    瀏覽(23)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法】八大排序

    【數(shù)據(jù)結(jié)構(gòu)與算法】八大排序

    初看這些概念可能一臉懵,但是沒有關(guān)系,等下面學(xué)完幾種排序之后在來看這些概念非常容易理解。 排序:所謂排序,就是使一串記錄,按照其中的某個或某些的大小,遞增或遞減的排列起來的操作。 穩(wěn)定性:假定在待排序的記錄序列中,存在多個具有相同的

    2024年02月01日
    瀏覽(22)
  • 【數(shù)據(jù)結(jié)構(gòu)】八大排序之簡單選擇排序算法

    【數(shù)據(jù)結(jié)構(gòu)】八大排序之簡單選擇排序算法

    ?? 個人主頁 :修修修也 ?? 所屬專欄 :數(shù)據(jù)結(jié)構(gòu) ?? 操作環(huán)境 : Visual Studio 2022 目錄 一.簡單選擇排序簡介及思路 二.簡單選擇排序的代碼實現(xiàn) 三.簡單選擇排序的優(yōu)化 四.簡單選擇排序的時間復(fù)雜度分析 結(jié)語 簡單選擇排序算法(Simple Selection Sort) 是一種簡單直觀的 選擇排序算

    2024年02月01日
    瀏覽(24)
  • 【數(shù)據(jù)結(jié)構(gòu)】--八大排序算法【完整版】

    【數(shù)據(jù)結(jié)構(gòu)】--八大排序算法【完整版】

    本文主要講解代碼及代碼思路,涵蓋八大排序的全面知識點 ———————————————— 目錄 一、直接插入排序 二、希爾排序(直接插入排序的改良版) 三、選擇排序(直接選擇排序) 四、堆排序 五、冒泡排序 六、快速排序 1、 左右指針法 2、挖坑法: 3、前后指針

    2024年02月16日
    瀏覽(29)
  • 【數(shù)據(jù)結(jié)構(gòu)】 常見的八大排序算法

    【數(shù)據(jù)結(jié)構(gòu)】 常見的八大排序算法

    排序有 內(nèi)部排序和外部排序 ,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,這里八大排序就是內(nèi)部排序,指直接插入,希爾,選擇,堆排,冒泡,快排,歸并,計數(shù)。 下面讓我們來共同學(xué)習(xí)這八大排序吧!?????? 什么是外部排序: 外排序是數(shù)據(jù)量較大,內(nèi)存放不下,數(shù)據(jù)放到外

    2024年02月12日
    瀏覽(96)
  • 第五章 數(shù)據(jù)結(jié)構(gòu)與算法——八大排序

    第五章 數(shù)據(jù)結(jié)構(gòu)與算法——八大排序

    目錄 一、排序的概念及其運用 二、八大排序的原理及其實現(xiàn)(升序為例) (一)、直接插入排序 (二)、希爾排序(也叫縮小增量排序)(重要) 1.原理: 2.該排序一般分為兩個步驟: 3.預(yù)排序過程: 4.預(yù)排序的意義(升序為例): 5.希爾排序的特點: 6.希爾排序代碼實現(xiàn)

    2024年02月19日
    瀏覽(29)
  • 數(shù)據(jù)結(jié)構(gòu)與算法之手撕排序算法

    數(shù)據(jù)結(jié)構(gòu)與算法之手撕排序算法

    為什么要學(xué)習(xí)排序算法? 根據(jù)統(tǒng)計,早起大型機(jī)CPU資源的四分之一都花在了數(shù)據(jù)排序上面。排序算法作為最基礎(chǔ)的算法,各種操作系統(tǒng)、編程語言都提供了內(nèi)置的實現(xiàn)。既然排序?qū)崿F(xiàn)隨處可見,我們?yōu)槭裁催€要自己動手實現(xiàn)呢?雖然經(jīng)典算法要動手寫寫加深印象的道理都懂,

    2023年04月16日
    瀏覽(22)
  • [ 數(shù)據(jù)結(jié)構(gòu) -- 手撕排序算法第二篇 ] 冒泡排序

    [ 數(shù)據(jù)結(jié)構(gòu) -- 手撕排序算法第二篇 ] 冒泡排序

    手撕排序算法系列之:冒泡排序。 從本篇文章開始,我會介紹并分析常見的幾種排序,大致包括 插入排序 , 冒泡排序 ,希爾排序,選擇排序,堆排序,快速排序,歸并排序等。 大家可以點擊此鏈接閱讀其他排序算法:排序算法_大合集(data-structure_Sort) 本篇主要來手撕冒

    2024年02月11日
    瀏覽(96)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕排序算法之插入排序與希爾排序

    【數(shù)據(jù)結(jié)構(gòu)與算法篇】手撕排序算法之插入排序與希爾排序

    ???內(nèi)容專欄:《數(shù)據(jù)結(jié)構(gòu)與算法篇》 ??本文概括: 講述排序的概念、直接插入排序、希爾排序、插入排序和希爾排序的區(qū)別。 ??本文作者:花 碟 ??發(fā)布時間:2023.6.13 排序 :所謂排序,就是使一串記錄,按照其中的某個或某些的大小,遞增或遞減的排列起來的

    2024年02月09日
    瀏覽(25)
  • 【數(shù)據(jù)結(jié)構(gòu)初階】八大排序算法+時空復(fù)雜度

    【數(shù)據(jù)結(jié)構(gòu)初階】八大排序算法+時空復(fù)雜度

    學(xué)會控制自己是人生的必修課 1.直接插入排序思想: 假設(shè)現(xiàn)在已經(jīng)有一個有序序列,如果有一個數(shù)字插入到這段序列的末尾,我們會選擇拿這個數(shù)和它前面的每個數(shù)字都比較一遍,如果前面的數(shù)字比他大,那我們就讓前面的數(shù)字賦值到這個被插入的數(shù)字位置,依次與前面的數(shù)

    2024年02月01日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包