Maison  >  Article  >  développement back-end  >  Résumé des algorithmes de tri de structures de données

Résumé des algorithmes de tri de structures de données

angryTom
angryTomoriginal
2019-11-01 09:39:154759parcourir

Résumé des algorithmes de tri de structures de données

Résumé de l'algorithme de tri de la structure des données

Vue d'ensemble

Le tri comporte un tri interne et un tri externe , Le tri interne consiste à trier les enregistrements de données en mémoire, tandis que le tri externe est dû au fait que les données triées sont très volumineuses et ne peuvent pas accueillir tous les enregistrements triés en même temps. Pendant le processus de tri, il faut accéder à la mémoire externe.

1. Tri par insertion - Tri par insertion directe

Idée de base :

Insérer un enregistrement dans un tableau trié Dans la table de séquence, un nouveau tri ordonné La liste est obtenue avec le nombre d’enregistrements augmenté de 1. Autrement dit : traitez d'abord le premier enregistrement de la séquence comme une sous-séquence ordonnée, puis insérez le deuxième enregistrement un par un jusqu'à ce que la séquence entière soit ordonnée.

Points clés : Configurez des sentinelles pour le stockage temporaire et le jugement des limites des tableaux.

S'il rencontre un élément égal à l'élément inséré, alors l'élément inséré place l'élément à insérer après l'élément égal. Par conséquent, l'ordre des éléments égaux n'a pas changé. L'ordre de la séquence non ordonnée d'origine est l'ordre après le tri, donc le tri par insertion est stable.

Implémentation de l'algorithme :

void print(int a[], int n ,int i){  
    cout<<i <<":";  
    for(int j= 0; j<8; j++){  
        cout<<a[j] <<" ";  
    }  
    cout<<endl;  
}  
  
  
void InsertSort(int a[], int n)  
{  
    for(int i= 1; i<n; i++){  
        if(a[i] < a[i-1]){               //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  
            int j= i-1;   
            int x = a[i];        //复制为哨兵,即存储待排序元素  
            a[i] = a[i-1];           //先后移一个元素  
            while(x < a[j]){  //查找在有序表的插入位置  
                a[j+1] = a[j];  
                j--;         //元素后移  
            }  
            a[j+1] = x;      //插入到正确位置  
        }  
        print(a,n,i);           //打印每趟排序的结果  
    }  
      
}  
  
int main(){  
    int a[8] = {3,1,5,7,2,4,9,6};  
    InsertSort(a,8);  
    print(a,8,8);  
}

Complexité temporelle : O(n^2).

Les autres tris par insertion incluent le tri par insertion binaire et le tri par insertion bidirectionnelle.

2. Tri par insertion—Hill Sort (Shell`s Sort)

Hill Sort a été proposé par D.L. Shell en 1959. Il est plus avancé que le tri direct. amélioration. Le tri Hill est également appelé tri incrémentiel réduit

Idée de base :

Divisez d'abord la séquence entière des enregistrements à trier en plusieurs sous-séquences pour un tri par insertion directe, et attendez les enregistrements dans le la séquence entière doit être " " Fondamentalement dans l'ordre ", alors tous les enregistrements sont directement insérés et triés dans l'ordre.

Méthode de fonctionnement :

Sélectionnez une séquence incrémentielle t1, t2,...,tk, où ti>tj, tk=1 en fonction du nombre de séquences incrémentielles k, effectuez k fois ; sur la séquence Tri ; à chaque passe de tri, la colonne à trier est divisée en plusieurs sous-séquences de longueur m selon l'incrément ti correspondant, et un tri par insertion directe est effectué sur chaque sous-table. Ce n'est que lorsque le facteur d'incrémentation est de 1 que la séquence entière est traitée comme un tableau et que la longueur du tableau est la longueur de la séquence entière.

Implémentation de l'algorithme :

On traite simplement la séquence incrémentale : la séquence incrémentale d = {n/2,n/4, n/8 .....1} n est le nombre de nombres à trier

c'est-à-dire : diviser d'abord l'ensemble des enregistrements à trier en plusieurs groupes de sous-séquences selon un certain incrément d (n/2, n est le nombre de nombres à trier), les indices enregistrés dans chaque groupe diffèrent de d. Effectuez un tri par insertion directe sur tous les éléments de chaque groupe, puis regroupez-les avec un incrément plus petit (d/2) et effectuez un tri par insertion directe dans chaque groupe. Continuez à réduire l'incrément jusqu'à ce qu'il atteigne 1, et enfin utilisez le tri par insertion directe pour terminer le tri.

void print(int a[], int n ,int i){ 
    cout<<i <<":"; 
    for(int j= 0; j<8; j++){ 
        cout<<a[j] <<" "; 
    } 
    cout<<endl; 
} 
/**
 * 直接插入排序的一般形式
 *
 * @param int dk 缩小增量,如果是直接插入排序,dk=1
 *
 */ 
   
void ShellInsertSort(int a[], int n, int dk) 
{ 
    for(int i= dk; i<n; ++i){ 
        if(a[i] < a[i-dk]){          //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入 
            int j = i-dk;    
            int x = a[i];           //复制为哨兵,即存储待排序元素 
            a[i] = a[i-dk];         //首先后移一个元素 
            while(x < a[j]){     //查找在有序表的插入位置 
                a[j+dk] = a[j]; 
                j -= dk;             //元素后移 
            } 
            a[j+dk] = x;            //插入到正确位置 
        } 
        print(a, n,i ); 
    } 
       
} 
   
/**
 * 先按增量d(n/2,n为要排序数的个数进行希尔排序
 *
 */ 
void shellSort(int a[], int n){ 
   
    int dk = n/2; 
    while( dk >= 1  ){ 
        ShellInsertSort(a, n, dk); 
        dk = dk/2; 
    } 
} 
int main(){ 
    int a[8] = {3,1,5,7,2,4,9,6}; 
    //ShellInsertSort(a,8,1); //直接插入排序 
    shellSort(a,8);           //希尔插入排序 
    print(a,8,8); 
}

L'analyse de la rapidité du tri Hill est difficile. Le nombre de comparaisons de codes clés et le nombre de mouvements d'enregistrement dépendent de la sélection de la séquence de facteurs d'incrément d. Dans certaines circonstances, le nombre de comparaisons de codes clés et d'enregistrements. les mouvements peuvent être estimés avec précision. Personne n'a encore donné de méthode pour sélectionner la meilleure séquence de facteurs incrémentaux. La séquence de facteurs incrémentaux peut être prise de différentes manières, y compris les nombres impairs et les nombres premiers. Cependant, il convient de noter qu'il n'y a pas de facteurs communs entre les facteurs incrémentaux sauf 1, et que le dernier facteur incrémentiel doit être 1. La méthode de tri Hill est une méthode de tri instable.

3. Tri par sélection—Tri par sélection simple

Idée de base :

Dans un ensemble de nombres à trier, sélectionnez Rechercher le plus petit (ou le plus grand) et l'échanger avec le nombre en première position ; puis trouver le nombre le plus petit (ou le plus grand) parmi les nombres restants et l'échanger avec le nombre en deuxième position, et ainsi de suite, jusqu'au n-1ème élément (le avant-dernier numéro) et le nième élément (le dernier numéro) sont comparés.

Méthode de fonctionnement :

Le premier passage, trouver l'enregistrement avec le plus petit code clé parmi n enregistrements et l'échanger avec le premier enregistrement

Le deuxième passage ; , sélectionnez l'enregistrement avec le plus petit code clé parmi les n-1 enregistrements à partir du deuxième enregistrement et échangez-le avec le deuxième enregistrement

et ainsi de suite...

i fois, puis ; sélectionnez l'enregistrement avec le plus petit code clé parmi les n-i+1 enregistrements à partir du i-ème enregistrement et échangez-le avec le i-ème enregistrement,

jusqu'à ce que la séquence entière soit ordonnée par le code clé.

Implémentation de l'algorithme :

void print(int a[], int n ,int i){  
    cout<<"第"<<i+1 <<"趟 : ";  
    for(int j= 0; j<8; j++){  
        cout<<a[j] <<"  ";  
    }  
    cout<<endl;  
}  
/** 
 * 数组的最小值 
 * 
 * @return int 数组的键值 
 */  
int SelectMinKey(int a[], int n, int i)  
{  
    int k = i;  
    for(int j=i+1 ;j< n; ++j) {  
        if(a[k] > a[j]) k = j;  
    }  
    return k;  
}  
  
/** 
 * 选择排序 
 * 
 */  
void selectSort(int a[], int n){  
    int key, tmp;  
    for(int i = 0; i< n; ++i) {  
        key = SelectMinKey(a, n,i);           //选择最小的元素  
        if(key != i){  
            tmp = a[i];  a[i] = a[key]; a[key] = tmp; //最小元素与第i位置元素互换  
        }  
        print(a,  n , i);  
    }  
}  
int main(){  
    int a[8] = {3,1,5,7,2,4,9,6};  
    cout<<"初始值:";  
    for(int j= 0; j<8; j++){  
        cout<<a[j] <<"  ";  
    }  
    cout<<endl<<endl;  
    selectSort(a, 8);  
    print(a,8,8);  
}

Amélioration du tri par sélection simple - tri par sélection binaire

Tri par sélection simple, chaque boucle ne peut déterminer qu'un seul élément après le tri du positionnement. On peut envisager d'améliorer le positionnement de deux éléments (les enregistrements maximum et minimum actuels) pour chaque cycle, réduisant ainsi le nombre de cycles nécessaires au tri. Après l'amélioration, pour trier n données, seules [n/2] boucles sont nécessaires au maximum. L'implémentation spécifique est la suivante :

void SelectSort(int r[],int n) {  
    int i ,j , min ,max, tmp;  
    for (i=1 ;i <= n/2;i++) {    
        // 做不超过n/2趟选择排序   
        min = i; max = i ; //分别记录最大和最小关键字记录位置  
        for (j= i+1; j<= n-i; j++) {  
            if (r[j] > r[max]) {   
                max = j ; continue ;   
            }    
            if (r[j]< r[min]) {   
                min = j ;   
            }     
      }    
      //该交换操作还可分情况讨论以提高效率  
      tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;  
      tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;   
  
    }   
}

4. Tri par sélection - Heap Sort (Heap Sort)

Le tri par tas est un tri par sélection arborescente, qui est une sélection directe. trier. Amélioration efficace.

Idée de base :

La définition du tas est la suivante : une séquence de n éléments (k1, k2,...,kn), si et seulement si

est satisfait

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

  (b)  小顶堆序列:(12,36,24,85,47,30,53,91)

初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。

因此,实现堆排序需解决两个问题:

  1. 如何将n 个待排序的数建成堆;

  2. 2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。

首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。

再讨论对n 个元素初始建堆的过程。

建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。

2)筛选从第个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

 算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

void print(int a[], int n){  
    for(int j= 0; j<n; j++){  
        cout<<a[j] <<"  ";  
    }  
    cout<<endl;  
}  
  
  
  
/** 
 * 已知H[s…m]除了H[s] 外均满足堆的定义 
 * 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,  
 * 
 * @param H是待调整的堆数组 
 * @param s是待调整的数组元素的位置 
 * @param length是数组的长度 
 * 
 */  
void HeapAdjust(int H[],int s, int length)  
{  
    int tmp  = H[s];  
    int child = 2*s+1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)  
    while (child < length) {  
        if(child+1 <length && H[child]<H[child+1]) { // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)  
            ++child ;  
        }  
        if(H[s]<H[child]) {  // 如果较大的子结点大于父结点  
            H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点  
            s = child;       // 重新设置s ,即待调整的下一个结点的位置  
            child = 2*s+1;  
        }  else {            // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出  
             break;  
        }  
        H[s] = tmp;         // 当前待调整的结点放到比其大的孩子结点位置上  
    }  
    print(H,length);  
}  
  
  
/** 
 * 初始堆进行调整 
 * 将H[0..length-1]建成堆 
 * 调整完之后第一个元素是序列的最小的元素 
 */  
void BuildingHeap(int H[], int length)  
{   
    //最后一个有孩子的节点的位置 i=  (length -1) / 2  
    for (int i = (length -1) / 2 ; i >= 0; --i)  
        HeapAdjust(H,i,length);  
}  
/** 
 * 堆排序算法 
 */  
void HeapSort(int H[],int length)  
{  
    //初始堆  
    BuildingHeap(H, length);  
    //从最后一个元素开始对序列进行调整  
    for (int i = length - 1; i > 0; --i)  
    {  
        //交换堆顶元素H[0]和堆中最后一个元素  
        int temp = H[i]; H[i] = H[0]; H[0] = temp;  
        //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整  
        HeapAdjust(H,0,i);  
  }  
}   
  
int main(){  
    int H[10] = {3,1,5,7,2,4,9,6,10,8};  
    cout<<"初始值:";  
    print(H,10);  
    HeapSort(H,10);  
    //selectSort(a, 8);  
    cout<<"结果:";  
    print(H,10);  
  
}

分析:

设树深度为k,从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:               

而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。

5. 交换排序—冒泡排序(Bubble Sort)

基本思想:

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

算法的实现:

void bubbleSort(int a[], int n){  
    for(int i =0 ; i< n-1; ++i) {  
        for(int j = 0; j < n-i-1; ++j) {  
            if(a[j] > a[j+1])  
            {  
                int tmp = a[j] ; a[j] = a[j+1] ;  a[j+1] = tmp;  
            }  
        }  
    }  
}

冒泡排序算法的改进

对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。

6. 交换排序—快速排序(Quick Sort)

基本思想:

1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。

3)此时基准元素在其排好序后的正确位置

4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

算法的实现:

 递归实现:

void print(int a[], int n){  
    for(int j= 0; j<n; j++){  
        cout<<a[j] <<"  ";  
    }  
    cout<<endl;  
}  
  
void swap(int *a, int *b)  
{  
    int tmp = *a;  
    *a = *b;  
    *b = tmp;  
}  
  
int partition(int a[], int low, int high)  
{  
    int privotKey = a[low];                             //基准元素  
    while(low < high){                                   //从表的两端交替地向中间扫描  
        while(low < high  && a[high] >= privotKey) --high;  //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端  
        swap(&a[low], &a[high]);  
        while(low < high  && a[low] <= privotKey ) ++low;  
        swap(&a[low], &a[high]);  
    }  
    print(a,10);  
    return low;  
}  
  
  
void quickSort(int a[], int low, int high){  
    if(low < high){  
        int privotLoc = partition(a,  low,  high);  //将表一分为二  
        quickSort(a,  low,  privotLoc -1);          //递归对低子表递归排序  
        quickSort(a,   privotLoc + 1, high);        //递归对高子表递归排序  
    }  
}  
  
int main(){  
    int a[10] = {3,1,5,7,2,4,9,6,10,8};  
    cout<<"初始值:";  
    print(a,10);  
    quickSort(a,0,9);  
    cout<<"结果:";  
    print(a,10);  
  
}

分析:

快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。

7. 归并排序(Merge Sort)

基本思想:

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

合并方法:

设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m。

j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束//选取r[i]和r[j]较小的存入辅助数组rf
如果r[i]否则,rf[k]=r[j]; j++; k++; 转⑵//将尚未处理完的子表中元素存入rf
如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空
如果j<=n ,  将r[j…n] 存入rf[k…n] //后一子表非空合并结束。

//将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n]  
void Merge(ElemType *r,ElemType *rf, int i, int m, int n)  
{  
    int j,k;  
    for(j=m+1,k=i; i<=m && j <=n ; ++k){  
        if(r[j] < r[i]) rf[k] = r[j++];  
        else rf[k] = r[i++];  
    }  
    while(i <= m)  rf[k++] = r[i++];  
    while(j <= n)  rf[k++] = r[j++];  
}

归并的迭代算法

1 个元素的表总是有序的。所以对n 个元素的待排序列,每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表长度均为2。再进行两两合并,直到生成n 个元素按关键码有序的表。

void print(int a[], int n){  
    for(int j= 0; j<n; j++){  
        cout<<a[j] <<"  ";  
    }  
    cout<<endl;  
}  
  
//将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n]  
void Merge(ElemType *r,ElemType *rf, int i, int m, int n)  
{  
    int j,k;  
    for(j=m+1,k=i; i<=m && j <=n ; ++k){  
        if(r[j] < r[i]) rf[k] = r[j++];  
        else rf[k] = r[i++];  
    }  
    while(i <= m)  rf[k++] = r[i++];  
    while(j <= n)  rf[k++] = r[j++];  
    print(rf,n+1);  
}  
  
void MergeSort(ElemType *r, ElemType *rf, int lenght)  
{   
    int len = 1;  
    ElemType *q = r ;  
    ElemType *tmp ;  
    while(len < lenght) {  
        int s = len;  
        len = 2 * s ;  
        int i = 0;  
        while(i+ len <lenght){  
            Merge(q, rf,  i, i+ s-1, i+ len-1 ); //对等长的两个子表合并  
            i = i+ len;  
        }  
        if(i + s < lenght){  
            Merge(q, rf,  i, i+ s -1, lenght -1); //对不等长的两个子表合并  
        }  
        tmp = q; q = rf; rf = tmp; //交换q,rf,以保证下一趟归并时,仍从q 归并到rf  
    }  
}  
  
  
int main(){  
    int a[10] = {3,1,5,7,2,4,9,6,10,8};  
    int b[10];  
    MergeSort(a, b, 10);  
    print(b,10);  
    cout<<"结果:";  
    print(a,10);  
  
}

两路归并的递归算法

void MSort(ElemType *r, ElemType *rf,int s, int t)  
{   
    ElemType *rf2;  
    if(s==t) r[s] = rf[s];  
    else  
    {   
        int m=(s+t)/2;          /*平分*p 表*/  
        MSort(r, rf2, s, m);        /*递归地将p[s…m]归并为有序的p2[s…m]*/  
        MSort(r, rf2, m+1, t);      /*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/  
        Merge(rf2, rf, s, m+1,t);   /*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/  
    }  
}  
void MergeSort_recursive(ElemType *r, ElemType *rf, int n)  
{   /*对顺序表*p 作归并排序*/  
    MSort(r, rf,0, n-1);  
}

更多PHP相关知识,请访问PHP中文网

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:fonction C externeArticle suivant:fonction C externe