排序算法

来源:互联网 发布:js 动态仪表盘 编辑:程序博客网 时间:2024/05/24 01:13

冒泡排序:o(n2)

0~n-1   两两比较,找出最大的,放在n-1位置;

0~n-2  两两比较,找出第二大的,放在n-2位置;

以此类推

class BubbleSort {
public:
    int* bubbleSort(int* A, int n) {
        // write code here
        int temp;
        int p=n;
        for(int j=0;j<n-1;j++)
      {
        bool finish = true;//某一次没做交换,说明已整体有序
        for(int i=0;i<p-1;i++)//一定要注意p的值
            {
            if(A[i]>A[i+1])
             {
                finish=false;
                temp=A[i];
                A[i]=A[i+1];
                A[i+1]=temp;
             }
           
            }
            if(finish)
                break;
            else
               p=p-1;
        }
        return A;
    }
};

需要注意的是,冒泡排序中需要进行n-1次比较,并且在第i次中比较到第n-i就可以了。

对于冒泡排序来说,最后面的都是排序好的!





选择排序:o(n2)

首先找到最小值,放在位置0;

再找到第二最小值,放在位置1;

以此类推

class SelectionSort {
public:
    int* selectionSort(int* A, int n) {
        // write code here
        int min;
        int temp;
        for(int j=0;j<n;j++)
       {
            min=A[j];
        for(int i=j+1;i<n;i++)
            {
            if(A[i]<min)
                {
                min=A[i];
                temp=A[i];
                A[i]=A[j];
                A[j]=temp;
                }
            }
       }
        return A;
    }
};

对于选择排序来说,前面第j个数永远都是剩下还没有排序的数中的最小的,所以剩余的数都和第j个数进行比较就可以了!!

class SelectionSort {
public:
    int* selectionSort(int* A, int n) {
         for (int i=0; i<n; i++){
            int location=i;
             for (int j=i+1; j<n;j++){
                if (A[location]>A[j]){
                    location=j;               
                    //swap(A[j],A[j+1]);
                }
            }
            swap(A[location],A[i]);
        }
        return A;
    }
};
但是比较上面两种代码,我们可以看出第二种明显比价简洁,原因就是第二种里面在进行比较的时候并没与立刻就进行交换,而是时刻记录当前比较出来的最小值的位置,直到最后才进行交换!!和第一种代码相比,第一种时刻都要进行交换,但其实最后得到的结果都是一样的,思想也是一样的,都是选出最小的值放到数组最前面的第j个位置!!!


插入排序:o(n2)

每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

class InsertionSort {
public:
    int* insertionSort(int* A, int n) {
        // write code here
        int j,temp;
        for(int i=0;i<n;i++)
            {
               j=i-1;
            while(j>=0)
                {
                if(A[i]<A[j])
                    {
                       temp=A[i];
                  A[i]=A[j];
                  A[j]=temp;
                       i=j;
                       j=i-1;//这里的1就是希尔排序中的步长,插入排序的步长为1
                    }else{
                    break;
                    }
                 }
            }
        return A;
        
    }
};

对于插入排序来说,最前面的都是排序好的!!

class InsertionSort {
public:
    int* insertionSort(int* A, int n) {
        int i,j;
        for (i = 1; i < n; i++) {
            int cur = A[i];
            for (j = i - 1; j >= 0 && cur < A[j]; j--) {
                A[j+1] = A[j];
            }
            A[j+1] = cur;
        }
        return A;
    }
};
但是,比较上面两个程序可知,第二个更简单,第二个程序时刻将当前需要插入比较的值cur单独拿出来,一一和前面的值进行比较,并不着急把它插入进去,直到最后确定前面没有比它小的了,才把它插入进去!!!

上面的时间复杂度都是o(n2);



下面介绍时间复杂度为o(n*logn)的算法:

归并排序;

快速排序;

堆排序;

希尔排序;



归并排序:多个长度为1的有序区间;

多个长度为1的相邻有序区间经过两两合并得到多个长度为2的有序区间;

以此类推;

主要用到递归的思想,合并操作。

class MergeSort {
public:
    int* mergeSort(int* A, int n) {
        // write code here
        int *p;
        int h,count,len,f;
        count=0;
        len=1;
        f=0;
        if(!(p=(int*)malloc(sizeof(int)*n)))//这里一定要用malloc分配内存,最后才能用free释放。并且不能将p的申请内存的函数放到MergeOne里面,否则要不断的为p分配内存
            //容易引发错误
            {
           // printf("内存分配失败");
            exit(0);
}
        while(len<n)
            {
            if(f==1)
                {
                MergeOne(p,A,n,len);
            }
            else{
            MergeOne(A,p,n,len);
            }
            len=len*2;
            f=1-f;
            
            count++;
        }
            //printf("第%d步排序结果是:",count);
            /*for(h=0;h<n;h++)
                {
                printf("%d",A[h]);
}
            printf("\n");
        }*/
        if(f)
            {
            for(h=0;h<n;h++)
                A[h]=p[h];
        }
        free(p);
        return A;
    }
    void MergeOne(int* A,int*b,int n,int len)
        {
        int i,j,k,s,e;
        s=0;
       while(s+len<n)
           {
           e=s+2*len-1;
           if(e>=n)
               {
               e=n-1;
}
           k=s;
           i=s;
           j=s+len;
           while(i<s+len&&j<=e)
               {
               if(A[i]<A[j])
                   {
                   b[k++]=A[i++];
  }else{
                   b[k++]=A[j++];
               }
  }
           while(i<s+len)
               {
               b[k++]=A[i++];
  }
           while(j<=e)
               {
               b[k++]=A[j++];
  }
           s=e+1;
       }
        if(s<n)
            {
            for(;s<n;s++)
                b[s]=A[s];
}
}
};

上面是归并排序的程序,这个程序首先要判断len的长度是否小于n,如果len长度正好为n或者大于n,那么排序就已经结束了;

然后在进行排序的时候,也就是在MergeOne函数中,s作为起始序列的位置,首先要判断起始序列后面的序列是否还有内容,如果

s+len等于n或者大于n,就代表只有起始序列有内容,后面第二个序列没有内容,所以不需要进行比较,直接将起始序列合并到已经

排好序的数组中就可以了,也就是下面的if(s<n)这一段;但是如果起始序列后面的序列还有内容,就需要进行比较判断了。当然首先

要计算中第二个序列的起始位置和结束位置,分别为s+len, s+2*len-1;当然如果第二个序列的长度不足len的话,也就是最后剩下的不足

len长度的序列,当然结束位置就是整个数组结束的位置,就是n-1;最后进行正常的比较合并就可以了。

class MergeSort {
public:
    int* mergeSort(int* A, int n) {
        // write code here
        int p[n];
        mergeOne(A,p,0,n-1);
        return A;//这里是重点!!!
    }
    void mergeOne(int* A,int* p,int start, int end){
        if(start>=end){
            return;
        }//这里非常重要,否则会一直递归下去
        int middle=(start+end)/2;
        mergeOne(A,p,start,middle);
        mergeOne(A,p,middle+1,end);
       // merging(A,p,start,middle,end);
    //}
   // void merging(int* A,int* p,int start,int middle,int end){
        int a=start;
        int b=middle+1;
        int k=start;
        while(a<=middle&&b<=end){
            if(A[a]<A[b])
                 p[k++]=A[a++];
            else
                p[k++]=A[b++];       
        }
        while(a<=middle)
            {
            p[k++]=A[a++];
}
        while(b<=end)
            {
            p[k++]=A[b++];
}
       
        for(int i=start;i<=end;i++)//这里是重点
            {
               A[i]=p[i];
        }
        //memcpy(A+start, p+start, (end - start + 1) * (sizeof(int) / sizeof(char)));
        }
};

上面的程序是利用递归思想进行的归并排序!!需要注意的是,最后将p的值赋值给A的时候,也可以借助于memcpy函数,效果都是一样的!!同时将比较合并的步骤单独写成一个函数void merging结果也是正确的!并且,对于数组p,用int* p=new int[n];进行定义也是可以的,只不过需要在return A的后面加上一句delete p;。




快速排序:也用到递归的思想

划分过程:主要思想是确定枢轴量之后,确定小于这个枢轴量的数的区间以及大于这个枢轴量的数的区间,然后再递归调用快速排序算法:

class QuickSort {
public:
    int* quickSort(int* A, int n) {
    quickSort(A, 0, n-1);
    return A;
    }
     
private:
    void quickSort(int* A, int low, int high) {
        if (low < high) {
            int pivot = partition(A, low, high);
            quickSort(A, low, pivot-1);
            quickSort(A, pivot+1, high);
        }
    }
     
    int partition(int* A, int low, int high) {
        int pivotkey = A[high]; 
        while (low < high) {
            while (low < high && pivotkey > A[low])
                  low++;
            swap(A[low], A[high]);
            while (low < high && A[high] >= pivotkey)
                high--;
            swap(A[low], A[high]);
        }
        return high;
    }
};

快速排序算法就是挖坑填数+分治法:

以一个数组作为示例,取区间第一个数为基准数。

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

初始时,i = 0;  j = 9;   X = a[i] = 72

由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;以此类推,上面的例子是摘抄自http://blog.csdn.net/morewindows/article/details/6684558。我认为挖坑填数这个形容非常贴切,对于上面的程序来说,一开始是将high作为枢轴量,如果想将low作为枢轴量,想一下挖坑填数,就是说,一开始在low的地方挖了一个坑,下一步就是从high向前数找到一个小于枢轴量的数来将low的地方的坑给填充上,这时候high地方出现了一个新的坑,以此类推,程序如下:

class QuickSort {
public:
    int* quickSort(int* A, int n) {
    quickSort(A, 0, n-1);
    return A;
    }
     
private:
    void quickSort(int* A, int low, int high) {
        if (low < high) {
            int pivot = partition(A, low, high);
            quickSort(A, low, pivot-1);
            quickSort(A, pivot+1, high);
        }
    }
     
    int partition(int* A, int low, int high) {
        int pivotkey = A[low]; 
        while (low < high) {
            while (low < high && pivotkey<A[high])
                  high--;
            swap(A[low], A[high]);
            while (low < high && A[low] <= pivotkey)
               low++;
            swap(A[low], A[high]);
        }
        return low;
    }
};


当然,还可以将中间的数作为枢轴量,要实现这个方便非常方便,直接将中间的数和第一个数进行交换就可以了。程序如下:

class QuickSort {
public:
    int* quickSort(int* A, int n) {
    quickSort(A, 0, n-1);
    return A;
    }
     
private:
    void quickSort(int* A, int low, int high) {
        if (low < high) {
            int pivot = partition(A, low, high);
            quickSort(A, low, pivot-1);
            quickSort(A, pivot+1, high);
        }
    }
     
    int partition(int* A, int low, int high) {
        int temp= A[(low+high)/2];
         A[(low+high)/2]=A[low];
        A[low]=temp;//将第一个数和中间数进行交换
        int pivotkey =A[low]; 
        while (low < high) {
            while (low < high && pivotkey<A[high])
                  high--;
            swap(A[low], A[high]);
            while (low < high && A[low] <= pivotkey)
               low++;
            swap(A[low], A[high]);
        }
        return low;
    }
};


堆排序:数组中的n个数建立成一个大小为n的最大堆,堆顶元素是所有元素中的最大值,将堆顶元素和堆的最后一个元素进行交换,

将堆顶元素脱离这个堆结构,放到数组的最后的位置;然后对剩余的n-1的堆从堆顶进行大根堆的调整,再找出这些值中的最大值于堆顶位置,然后和堆最后的元素进行交换,.......,当堆大小变成1,排序结束。

堆的存储:一般用数组来表示堆,i节点的父节点下标就是(i-1)/2,因此它的左右节点下标分别为2*i+1和2*i+2。

堆的插入:向堆中插入数据时 是将数据插入到数组最后,调整的时候是自下向上调整;

堆的删除:删除堆中的数据每次都只能删除数组中第0个数据,即将最后一个数据的值赋给根节点,然后再从根节点开始进行一次从上向下的调整,调整时先在左右儿子中找最小的,如果父节点比这个最小的子节点还小说明不需要调整了;反之将父节点个和它交换后再考虑后面的节点;

堆化数组:就是从上向下调整堆的过程,那么从哪个节点开始调整呢?一定是从后面开始数第一个有子节点的节点开始的,这个节点就是最后一个节点也就是第n-1个节点的父节点!!!根据前面的公式第n-1个节点的父节点为(n-1-1)/2也就是n/2-1。需要注意的是:注意一定是从上向下进行彻底的调整,不能只调整当前节点和它的左右子节点,而应该调整当前节点和它的左右子节点之后,如果真的发生的交换,那么一定要继续向下调整!!否则很有可能导致错误!!

堆排序:由上面的知识可知,堆排序的过程就是先堆化数组,然后取出第0个数据,然后删除第0个元素,调整堆,重复上述步骤,直至堆中只有一个数据时就直接取出这个数据。

class HeapSort {
public:
    int* heapSort(int* A, int n) {
        // write code here
        int i=(n-1-1)/2;
        //下面是调整数组成为最大堆数组的函数
        for(;i>=0;i--)
        MaxHeapFixdown( A, i,n);//数组大小为n,从第i个数组开始调整
         //下面是将堆中的数据从第0个数据开始输出,然后删除调整的函数,也就是说将第0个数据和最后一个数据进行交换,然后对堆进行从上到下的调整
        for(int j=n-1;j>=1;j--){
            int temp=A[0];
            A[0]=A[j];
            A[j]=temp;
            //swap(A[0],A[j]);
            MaxHeapFixdown(A,0,j);//将第一个数据和最后一个数据交换之后,最后的数据就是最大的数据,不需要参与到堆的调整了
        }
       
        
        return A;
        
    }
    void MaxHeapFixdown( int* A, int i,int n)
        {
            int max;
            int current=A[i];
        while(2*i+1<n){
                max=2*i+1;
                if(max+1<n&&A[max]<A[max+1])
                    max=max+1;
                if(current<A[max]){//这里是current和当前最大值进行比较!!!!一定要记住!!!
                    A[i]=A[max];
                    i=max;
                }else{
                    break;//注意这个位置一定要有break  
                }
         }
        A[i]=current;
}
};

上面的堆排序的程序,这个程序主要分两个步骤,首先是堆用数组表示,当然这是一定给定的就是一个数组;然后将堆调整成最大堆,也就是函数MaxHeapFixdown,这里的调整时从上向下调整的,这个函数非常非常重要!!!而调整的顺序是从倒数第一个有孩子节点的节点开始的!!最大堆调整结束之后,就使排序了!每次将当前最大值也就是第0个元素和最后一个元素交换,这样最大值就被放在数组后面的位置了!!

补充一点,因为向堆中插入数据都是插入到最后面的,所以之后对堆的调整应该是从下到上的调整;而删除堆的数据通常是删除第一个元素,然后将最后的元素放到第0个元素的位置,所以是从上向下调整;而堆化数组也是从上下向下调整的,只不过是从倒数第一个有子节点的节点开始的!!


希尔排序:比较特殊!!插入排序改良的算法,步长经过了调整,插入排序步长为1,希尔排序步长是动态从大到小调整的!!之所以说插入排序的步长为1,是因为他每次都是和前一个值进行比较,如果小则进行交换,然后再和前一个值进行比较,直到不能交换停止;而希尔排序中,每次都是和第前n个值进行比较,n就是步长!!!n步长结束之后,会进行下一轮的步长为n-1的调整;以此类推,直到最后步长为1,也就是插入排序的调整!关键在于步长的选择!!!

class ShellSort {
public:
    int* shellSort(int* A, int n) {
        // write code here
        int shell=n/2;
        while(shell>=1){
        shellSort1(A,shell,n);
            shell=shell/2;
        }
        return A;
    }
    void shellSort1(int* A,int shell,int n){
        for(int i=0;i<n;i++){
            int j=i-shell;//shell为增量,直接插入排序的时候为1
            int temp=i;
            int current=A[i];
            while(j>=0){
                if(current<A[j]){
                    A[temp]=A[j];
                    j=j-shell;
                    temp=temp-shell;  
                }else{
                    break;
                }
            }
          A[temp]=current; 
        }
        }
};

对于上面的程序来说,其实shellSort1函数中,shell为1的时候就是插入排序。





原创粉丝点击