Structure des données - résumé des algorithmes de tri (tri par insertion, tri par échange, tri par sélection, tri par fusion bidirectionnel, tri par base)

Sauf indication contraire, le résultat du tri par défaut est croissant. 

1. Tri par insertion

1.1 Algorithme de tri par insertion directe

Pour le tri par insertion directe, soit A[n] un tableau avec n nombres à trier, et supposons maintenant que la position à trier est i, et que le processus d'exécution est (la 0ème position n'est pas applicable, à partir de la 1ère) :

1. Enregistrez d'abord le i-ème élément, placez le i-ème élément en position 0 et agissez comme une sentinelle, ce qui peut réduire la condition de boucle, et relativement parlant, il sera un peu optimisé;

2. 1—Les éléments à la position i-1 ont été triés, comparez l'élément à la position i avec les premiers éléments i-1 tour à tour, et arrêtez-vous jusqu'à ce que vous trouviez un élément inférieur ou égal à vous-même, en supposant la position est k.

3. Déplacez tous les éléments aux positions k+1—i-1 vers l'arrière et placez le i-ème élément dans la position libérée k+1.

Code spécifique :

//直接插入排序 
void InsertSort(int a[],int n)
{
	int j;
	for(int i=2;i<=n;i++)
	{
		if(a[i]<a[i-1])        //当前元素比其前驱小,将a[i]插入有序表中 
		{
			a[0]=a[i];            //保存当前元素,充当哨兵 
			for(j=i-1;a[j]>a[0];j--)        //从后往前寻找待插入位置 
			{
				a[j+1]=a[j];                //向后移动 
			}
			a[j+1]=a[0];               //将第i个元素插入 
		}
	}
}

Complexité spatiale : O(1) ;

complexité temporelle :

Meilleur cas : O(n); //Le tableau d'origine est trié, et chaque fois qu'un élément est inséré, il n'a besoin d'être comparé qu'une seule fois sans se déplacer ;

Dans le pire des cas : O(n); //La table d'origine est également ordonnée, mais dans l'ordre inverse. Le nombre total de comparaisons est maximisé, et le nombre de déplacements est également maximisé.

Complexité temporelle moyenne : O(n^2) ;     

Stabilité : Chaque fois que l'élément est déplacé de l'arrière vers l'avant, il n'y aura aucun changement dans la position relative du même élément, stable.

Le tri par insertion convient à la fois au stockage séquentiel et aux tables linéaires pour le stockage lié. Mais le stockage chaîné ne fait que réduire le nombre de déplacements, le nombre de comparaisons est toujours de l'ordre du carré, et la complexité temporelle est toujours de l'ordre du carré.

Mais il convient que les éléments à trier soient essentiellement ordonnés. 

1.2 Tri par insertion binaire

Le tri par demi-insertion est une petite amélioration de l'algorithme de tri par insertion directe. L'amélioration est que lors de l'insertion du i-ème élément, les premiers éléments i-1 sont triés, donc dans l'étape de recherche, nous pouvons Utiliser demi-trouver pour trouver la position à insérer, puis déplacez-vous.

Code spécifique :

//折半插入排序
void InsertSort(int a[],int n)
{
	int i,j,low,high,mid;
	for(i=2;i<=n;i++)
	{
		a[0]=a[i];
		low=1,high=i-1;
		while(low<=high)       //折半查找的思想,寻找待插入位置 
		{
			mid=(low+high)/2;
			if(a[mid]<=a[0])
			{
				low=mid+1;
			}
			else
			high=mid-1;
		}
		for(j=i-1;j>=high+1;j--)          //将low —i-1位置的元素后移 
		{
			a[j+1]=a[j];
		}
		a[high+1]=a[0];         //插入到low的位置 
	}
}

Complexité temporelle : Bien que le nombre de comparaisons soit réduit par la demi-recherche, le nombre de coups reste inchangé. Par conséquent, la complexité temporelle de la demi-recherche est toujours O(n^2) ;

Lors du traitement de la demi-recherche, nous accordons une attention particulière à la situation où les valeurs des éléments sont égales. Lorsque a[mid]=a[i], nous continuons à rechercher une position appropriée. L'avantage de ceci est qu'il peut assurer la stabilité du tri par insertion .

2. Tri en colline 

Le tri Hill est une amélioration par rapport au tri par insertion directe. Comme la complexité temporelle du tri direct est relativement faible lorsque le tableau est relativement ordonné, l'idée du tri Hill est la suivante : diviser d'abord la liste à trier en plusieurs formes telles que : a[i, i+d, i+2d ... …] de la sous-table "spéciale", c'est-à-dire pour former une sous-table avec des enregistrements séparés par un certain incrément, et insérer et trier directement chaque sous-table. Lorsque les éléments de la table entière sont dans « ordre de base », puis Effectuez un tri par insertion directe sur tous les enregistrements.

En fait, le tri de Hill ajoute juste un incrément, chaque fois que les éléments d'un même incrément sont comparés, après chaque tour de tri, l'incrément est divisé par deux, mais l'idée de base est d'insérer directement le tri. Faites attention aux conditions de jugement du mouvement du tri Hill.

Code spécifique :

//希尔排序
void ShellSort(int a[],int n)
{
	int d;         //增量
	for(d=n/2;d>=1;d/=2)     //不断缩短增量排序 
	{
		for(int i=d+1;i<=n;i+=d)      //前面元素是排好序的,从d+1开始 
		{
			if(a[i]<a[i-d])
			{
				a[0]=a[i];              //这里的a[0]不是起到哨兵的作用,只是保存第i个元素 
				for(int j=i-d;j>0&&a[0]<a[j];j-=d)      //注意判定条件,将同一个子表中符合条件的元素后移 
				{
					a[j+d]=a[j];
				}
				a[j+d]=a[0];           //插入元素 
			}
		}
	} 
 } 

Complexité temporelle : pire cas (lorsque d vaut 1 : O(n) ; lorsque n est dans une certaine plage, la complexité temporelle est d'environ : O(n^1,3).

Il ressort du processus de tri que le tri de Hill est un algorithme de tri instable. Et parce qu'il a besoin d'utiliser l'incrément d pour trouver des éléments appartenant à la même sous-liste, il ne peut pas utiliser une liste chaînée, une structure de stockage qui n'a pas de caractéristiques d'accès aléatoire.

 3. Tri à bulles

Le tri à bulles est une idée basée sur l'échange. Chaque passage trie la valeur maximale ou minimale vers l'avant. Après chaque passage, l'élément à chaque position est sa position finale.

 //冒泡排序
void BubbleSort(int a[],int n)
{
 	for(int i=0;i<n-1;i++)      
 	{
 		bool flag=false;          //表示本趟冒泡排序是否发生交换的标志 
		for(int j=0;j<n-1-i;j++)  
		{
			if(a[j]>a[j+1])          //若逆序 
			{
				swap(a[j],a[j+1]);         //则交换 
				flag=true;
			}
		}
		if(flag==false)             //本趟遍历没有发生交换,说明已经有序 
		return ;	
	}
}

4. Tri rapide 

Le tri rapide est basé sur l'idée de diviser pour mieux régner : chaque fois qu'un nombre est sélectionné dans le tableau à trier comme référence, et la liste à trier est divisée en deux parties par une passe de tri. tri, les éléments plus petits que le repère sont à gauche, les éléments plus grands que le repère sont à droite, puis les côtés gauche et droit sont triés de manière récursive jusqu'à ce que chaque partie n'ait qu'un seul élément ou soit vide.

Dans ce code, on suppose que le premier élément est utilisé comme base de division à chaque fois.

Code spécifique :

#include <iostream>
using namespace std;
int s[100];

int Partition(int a[],int low,int high)
{
	int p=a[low];               //选择基准元素 
	while(low<high)
	{
		while(low<high&&a[high]>=p)   //把基准后面比基准大的元素往前移 
		high--;
		a[low]=a[high];
		while(low<high&&a[low]<p)       //把基准前面比基准小的元素往后移 
		low++;
		a[high]=a[low];
	}
	a[low]=p;                  //把基准放在最终位置 
	return low;                //返回基准位置 
} 

void  QuickSort(int a[],int low,int high)
{
	if(low<high)
	{
		int mid=Partition(a,low,high);
		QuickSort(a,low,mid-1);          //对每次划分的前后部分分别递归处理 
		QuickSort(a,mid+1,high);
	}
}
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>s[i];
	QuickSort(s,0,n-1);
	for(int i=0;i<n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

On peut voir que la clé de l'algorithme de tri rapide réside dans le partitionnement, de sorte que les performances de l'algorithme dépendent principalement de la qualité de l'opération de partitionnement. Le tri rapide doit utiliser la récursivité, donc une pile de travail récursive est nécessaire comme espace auxiliaire.

La complexité spatiale est l'espace requis par la pile, dans le meilleur des cas : O(logn) ; dans le pire des cas, la table est fondamentalement ordonnée ou fondamentalement inversée. La complexité spatiale est O(n), la complexité spatiale moyenne est : O(logn) ;

Complexité temporelle : meilleur cas : O(nlogn), pire cas : O(n) ;

On peut en déduire que le tri rapide est un algorithme instable.

Bien que le tri rapide ne génère pas de sous-séquences ordonnées après chaque tri, la référence sera placée dans sa position finale après chaque tri.

5. Tri de sélection 

5.1 Tri de sélection simple 

Idée d'algorithme : Chaque tri consiste à sélectionner l'élément avec le mot-clé le plus petit (ou le plus grand) parmi les éléments à trier et à l'ajouter à la sous-séquence ordonnée.

Code spécifique :

//简单排序
void SelectSort(int a[],int n)
{
	for(int i=0;i<n-1;i++)     //n个元素需要n-1趟排序 
	{
		int min=i;
		for(int j=i+1;j<n;j++)       //每次都是寻找最小值 
		{
			if(a[j]<a[min])
			min=j;
		}
		if(min!=i)              //若找到最小值,交换 
		swap(a[i],a[min]);
	}
 } 

Complexité spatiale : O(1) ;

Complexité temporelle : O(n*n) ;

Cet algorithme n'est pas stable, mais il est applicable à la fois aux listes ordonnées et aux listes chaînées.

5.2 Tri par tas 

Le tri par tas est un algorithme aux performances relativement bonnes. Il est également basé sur l'idée du tri par sélection. Grand tas supérieur : dans un arbre binaire complet, la racine >= les nœuds gauche et droit. Les petits pieux supérieurs sont tout le contraire.

Le tri de tas est divisé en : la création d'un tas et la sortie de l'élément supérieur du tas, puis la poursuite de l'ajustement du tas.

Le processus de construction d'un tas (en prenant le gros tas comme exemple) : en partant du dernier nœud non terminal de l'arbre binaire complet, c'est-à-dire le n/2e nœud, si le nœud a des enfants gauche et droit, alors juger la gauche et la droite du nœud Si l'enfant est plus grand que le nœud racine, si c'est le cas, choisissez le plus grand nœud enfant à échanger avec le nœud racine, mais le tas de ses nœuds enfants peut être détruit après l'échange, alors continuez à utilisez la méthode tout à l'heure pour construire le prochain niveau de tas, jusqu'à ce que le sous-arbre enraciné à ce nœud forme un grand tas supérieur.

Processus de tri (prenons l'exemple du gros tas) : à chaque tri, l'élément supérieur du tas est ajouté à la sous-séquence ordonnée (échangé avec le dernier élément de la séquence d'éléments à trier), et la séquence d'éléments à trier est ajusté au chapiteau à nouveau tas.

Le code spécifique du big top heap :

#include <iostream>
using namespace std;
int s[100];
//大根堆排序
void HeadAdjust(int a[],int k,int len)
{
	a[0]=a[k];          //先把第k个元素存着,以期后面找到合适的位置放置
	for(int i=2*k;i<=len;i*=2)          //顺着k较大的子结点向下筛选 
	{
		if(i<len&&a[i]<a[i+1])       //i<len保证有右孩子,在左孩子和右孩子中选一个最大 
		{
			i++;
		} 
		if(a[0]>=a[i])        //根结点本身就是最大 
		break; 
		else
		{
			a[k]=a[i];           //根结点替换为最大的孩子 
			k=i;                  //孩子结点的大顶堆有可能被破坏,所以沿着k的孩子结点往下判断 
		}
	} 
	a[k]=a[0];                  //被筛选结点的值放入最终位置 
}

void BuiltMaxHeap(int a[],int len)       
{
	for(int i=len/2;i>=1;i--)       //从最后一个非终端结点到根结点调整堆 
	{
		HeadAdjust(a,i,len);
	}
 } 

void HeapSort(int a[],int len)
{
	BuiltMaxHeap(a,len);              //先建堆 
	for(int i=len;i>1;i--)       //n个元素需要进行n-1趟排序 
	{
		swap(a[i],a[1]);           //和堆低元素交换,将最大值后移,实现升序 
		HeadAdjust(a,1,i-1);     
	}
 } 
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>s[i];
	HeapSort(s,n);
	for(int i=1;i<=n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

Le processus d'analyse du petit tas supérieur est similaire à celui du grand tas supérieur, de sorte que le code est directement donné ici. Il convient de noter que le tri basé sur le grand tas supérieur est en ordre croissant, et le tri basé sur le petit tas supérieur est en ordre décroissant.

 Le code spécifique du petit tas supérieur :

#include <iostream>
using namespace std;
int s[100];
//小根堆排序
void HeadAdjust(int a[],int k,int len)
{
	a[0]=a[k];          //先把第k个元素存着,以期后面找到合适的位置放置
	for(int i=2*k;i<=len;i*=2)          //顺着k小的子结点向下筛选 
	{
		if(i<len&&a[i]>a[i+1])       //i<len保证有右孩子,在左孩子和右孩子中选一个最小 
		{
			i++;
		} 
		if(a[0]<=a[i])        //根结点本身就是最小 
		break; 
		else
		{
			a[k]=a[i];           //根结点替换为最小的孩子 
			k=i;                  //孩子结点的小顶堆有可能被破坏,所以沿着k的孩子结点往下判断 
		}
	} 
	a[k]=a[0];                  //被筛选结点的值放入最终位置 
}

void BuiltMinHeap(int a[],int len)       
{
	for(int i=len/2;i>=1;i--)       //从最后一个非终端结点到根结点调整堆 
	{
		HeadAdjust(a,i,len);
	}
 } 

void HeapSort(int a[],int len)
{
	BuiltMinHeap(a,len);              //先建堆 
	for(int i=len;i>1;i--)       //n个元素需要进行n-1趟排序 
	{
		swap(a[i],a[1]);           //和堆低元素交换,将最小值后移,实现降序 
		HeadAdjust(a,1,i-1);     
	}
 } 
int main(int argc, char** argv) {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>s[i];
	HeapSort(s,n);
	for(int i=1;i<=n;i++)
	{
		cout<<s[i]<<" "; 
	}
	return 0;
}

Complexité spatiale : O(1) ;

La complexité temporelle est : meilleur, pire et moyen sont O(nlogn);

On peut en déduire que l'algorithme est instable. 

6. Tri par fusion 

 L'idée de l'algorithme de tri par fusion : traiter n enregistrements de la table à trier comme n sous-tables ordonnées, mais la longueur de chaque sous-table est de 1, puis les fusionner par paires pour obtenir n/2 et prendre la toute la longueur jusqu'à 1 Ou une liste ordonnée de 2, continuez à fusionner deux par deux, et répétez jusqu'à ce qu'elle soit fusionnée en une liste ordonnée de longueur n, il s'agit d'un tri par fusion à 2 voies.

Code spécifique :

#include <bits/stdc++.h>
using namespace std;

#define n 100
int s[n];

int *b=(int *)malloc(sizeof(int)*(n+1));       //辅助数组B
int i,j,k;
void Merge(int a[],int low,int mid,int high)
{
	for(k=low;k<=high;k++)           //先将表中数据复制到辅助数组中 
	{
		b[k]=a[k];
	}
	for(i=low,j=mid+1,k=i;i<=mid&&j<=high;)
	{
		if(b[i]<=b[j])
		{
			a[k++]=b[i++];
		}
		else
		{
			a[k++]=b[j++];
		}
	}
	while(i<=mid)         //其中有一个表中还有数据 
	a[k++]=b[i++];
	while(j<=high)
	a[k++]=b[j++];
} 
void MergeSort(int a[],int low,int high)
{
	if(low<high)
	{
		int mid=(low+high)/2;          //从中间划分两个子序列 
		MergeSort(a,low,mid);          //对左侧子序列进行递归排序 
		MergeSort(a,mid+1,high);      //对左侧子序列进行递归排序 
		Merge(a,low,mid,high);        //归并 
	}
}
int main(int argc, char** argv) {
	int m;
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>s[i];
	}
	MergeSort(s,1,m);
	for(int i=1;i<=m;i++)
	cout<<s[i]<<" ";
	return 0;
}

L'opération de base du tri par fusion consiste à fusionner deux séquences triées dans un tableau en une seule.

Complexité spatiale : O(n) ; bien que la récursivité nécessite également de l'espace, l'arbre de fusion peut être considéré comme un arbre binaire inversé. profondeur de récursivité <logn ;

Complexité temporelle : O(nlogn).

7. Tri par base 

Le tri par cardinalité n'est pas basé sur l'idée de comparaison et de mouvement, mais le tri est basé sur la taille de chaque mot-clé.

L'idée de base du tri par base (en prenant la séquence décroissante comme exemple):

1. Initialisation : Mettre en place r files vides, Qr-1, Qr-2,..., Q0, selon l'ordre de poids décroissant de chaque bit de clé (un, dix, cent), faire "l'allocation" pour chaque clé peu de d » et « Collecter ».

2. Allocation : analysez chaque élément de manière séquentielle, si le bit de clé actuellement traité = x, puis insérez l'élément à la fin de la file d'attente Qx.

3. Collecte : retirer de la file d'attente et relier les nœuds dans chaque file d'attente de Qr-1, Qr-2, ..., Q0 en séquence.

Complexité spatiale : puisque le tri par base est stocké dans une chaîne. L'espace auxiliaire est requis pour r files d'attente. La complexité en temps est donc O(r).

Complexité temporelle : O(d(n+r)) ; le tri par base nécessite une allocation et une collecte d, une allocation nécessite O (n) et une collecte nécessite O (r).

On peut en déduire que le tri par base est stable.

Je suppose que tu aimes

Origine blog.csdn.net/m0_51769031/article/details/125459392
conseillé
Classement