快排 + 堆排 + 归并

三个重要的排序:快排 + 堆排 + 归并

快排

十分注意:快排是建立在三数取中法之上的,没有三数取中的快排什么都不是

时间复杂度:O(n*log n);

另外:快排有许多注意的细节,详见代码注释关键字 的位置也会影响结果。

#include <stdio.h>

void Swap(int* a, int* b)
{
  int tmp =*a;
  *a = *b;
  *b = tmp;
}

int GetMid(int* a, int begin, int end)
{
  int mid = begin +( (end - begin) >> 1);
  if(a[begin] > a[mid])
  {
    if(a[mid] > a[end])
      return mid;
    if(a[begin] > a[end])
      return a[end];
    else 
      return begin;
  }
  else // a[begin] < a[mid] 
  {
    if(a[begin] >a[end])
      return begin;
    if(a[mid] < a[end])
      return mid;
    else 
      return end;
  }
}


//内部一趟  end  做关键字
int PartQsort(int* a,int begin, int end)//partsort 返回 key 的位置
{

  //end  作关键字 
  int mid =GetMid(a,begin,end);//三数取中法 ,防止 最坏时间复杂度,即 防止数据有序 :思想:将有序数组先打乱,拿到中位数 做key 再排序
  Swap(&a[end], &a[mid]);

  int key = end;//注意: 选关键字的位置 决定了 先从哪边开始,选关键字为end,先从  左开始, 选关键为begin, 先从 右开始

  //为什么 ? 开始的方向  决定了 相遇点的数据 跟key的交换   ,而key   又决定了 开始走的方向 
  while(begin < end)
  {
    //end  做key 
    //左边 先走
    while(begin < end  && a[begin] <= a[key])   //begin 找大
    {
      ++begin;
    }
    //右边后走
    while(begin < end  && a[end] >= a[key])  //end找小
    {
      --end;
    }
    Swap(&a[begin], &a[end]);
  }
  Swap(&a[key] , &a[end]);//begin ==  end时  将a[begin]   和 a[key]  交换

  return begin;
  //return end;一样
}


//begin 做关键字
int PartQSort(int* a, int begin, int end)
{
  int key = begin;
  int mid =GetMid(a, begin, end);
  Swap(&a[key], &a[mid]);

  while(begin < end)
  {
    while(begin < end && a[end] >= a[key])//begin  做关键字 右边先走, 右边找小于a[key]的
    {
      --end;
    }

    while(begin < end  && a[begin] <= a[key] )  // 左边后走,找大于a[key]的
    {
      ++begin;
    }

    Swap(&a[begin], &a[end]);
  }
  Swap(&a[key], &a[begin]);
  return begin;//返回交换后关键字的位置//或者相遇点的位置
}
//单趟复杂度, O(n);


//外部大循环
void Qsort(int* a, int left, int right)  //递归  重复进行PartQSort
{
  if(left >= right)//因为 right = keyIndex-1 ;所以 left 也可能大于 right
    return;

  //int  keyIndex = PartQsort(a,left, right);
  int  keyIndex = PartQSort(a,left, right);

  QSort(a, left, keyIndex -1);
  QSort(a,keyIndex + 1, right);

  //  PartQsort(a, left, keyIndex -1);
  // PartQsort(a,keyIndex + 1, right);
}

//看了外部大循环 类似于二叉树的递归 ,所以时间复杂度为O(log N)
//最坏情况 有序 ,还要排,这样就会一直找,所以最坏 0(n*n)  即 ,每次都取了最末端
//最好情况  每次 keyIndex 都选中了中位数 O(n*logN)
//有序排序 最坏 ,那么快排怎么避免 最坏情况?   三数取中法 ,即 关键字取中间的那个数
int main()
{
  int a[] ={49, 38, 65, 97, 76, 13, 27, 49};
  int n = sizeof(a) / sizeof(int);
  Qsort(a,0,n-1);
  for(int i=0; i<n; ++i)
  {
    printf("%d\t",a[i]);
  }
  printf("\n");
  return 0;
}

2 挖坑法:

partSort2:

void PartSort2(int* a, int begin, int end)
{
int mid = GetMid(int*a , int begin, int end);
Swap(&a[mid], &a[begin]);//key在begin 就 交换a[begin]  , key  在 end  就 交换 a[end];
int key = a[begin];


//左边做key
while(begin < end)
{
while(begin < end && a[end] >= key])
{
--end;
}
a[begin] = a[end];// end 替代begin成为新坑

while(begin < end && a[begin]  <= key )//找大于
{
++begin;
}
}
a[begin] = key; //begin==end  填坑
}

挖坑法 实质跟上边那个一样。

3 前后指针法:

我只写了 内层的排序 ,外层还是递归一趟一趟。

key 必须选最右边的

int Part3(int* a, int begin ,int end)
{
int key = end;
int key = cur;
int prev = begin -1;
whlie( cur < key)
{
if (a[cur] < a[key] )  //比 关键字小
{
++prev;
Swap(&a[prev,  &a[cur]]);
++cur;
}
else //比关键字大
{
++cur;
}
}
Swap(&a[++prev], &a[key]);
return prev;
}



代码优化:



```c
int part3(int* a, int begin, int end)
{
int key = end;
int prev = begin -1;
int cur =begin;
while(cur < end)
{
if( a[cur] < a[key]  && ++prev != cur)
// ++prev ==  cur 时 ,交换也没效果 
	Swap(&a[prev], &a[cur]);
	
	++cur;

}
Swap(&a[++prev], &a[key]);
return prev;
}

堆排序

#include <stdio.h>


void Swap(int* a, int* b)
{
  int tmp =*a;
  *a = *b;
  *b =tmp;
}

void AdjustDown(int* a,int n ,int root)
{
  int parent = root;
  int child = parent * 2 + 1;

  while(child <n)
  {

    if( child + 1< n    &&    a[child] < a[child+1] )//child + 1  防止越界
    {
      ++child;
    }
    if(  a[child] > a[parent] )//孩子大于父亲   则进行 交换

    {
      Swap(&a[child], &a[parent]);
      parent =child;
      child = parent * 2 + 1;
    }

    else  //  大孩子小于父亲 ,结束
    {
      break;
    }
  }
}
void Heap(int* a,int n)
{

  //建堆
  for(int i= (n-1-1) /2; i >= 0; --i)// (n-1)/2  为 最后一个非叶子节点的公式
  {
    AdjustDown(a,n,i);
  }
  //现在  我们建好了一个大根堆

  //排序  升序
  int end = n-1;
  while(end >= 0)
  {
    Swap(&a[0], &a[end]);
    AdjustDown(a,end,0);
    --end;
  }
}



void Print(int* a,int n)
{
  for(int i =0; i<n; ++i)
  {
    printf("%d\t",a[i]);
  }
  printf("\n");
}


//大堆
int main()
{
  int a[9] ={2,3,76,3,45,3,6,65,676};
  int n =sizeof(a) / sizeof(int);
  Heap(a,n);
  Print(a,n);

  return 0;
}

堆排序 经常被用在求解Top K问题 ;
例如 : 荣耀战力 全国排名前十的 李白

这时我们找最大的前十个 ,所以 我们建小堆,
建小堆可以让大于堆顶的数据进入堆,进行堆顶元素出堆,新元素进队,(出堆内部蕴含AdjustDown和 进堆蕴含 AdjustUp算法);

为什么不建大堆,大堆导致只有比堆顶数据大的元素才能进堆,堆顶元素出堆之后 ,是不是不对了? 细想下,这样只能保证拿出一个最大的元素。

相反 找战力最低的是玩家,建大堆,

数据小于堆顶 ,进堆(堆顶出堆,新元素入堆,向上向下调整)

小数据就可以

核心: 我们找到的元素 ,最后都是进行与堆的最后一个元素交换的方式出堆的

3。归并排序


```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void Merge(int *a, int begin, int end ,int* tmp)
{
  if(begin >= end)
    return;
  int mid = begin + ((end - begin)>>1);


  Merge(a, begin, mid, tmp);
  Merge(a, mid+1, end, tmp);
  
  int begin1 = begin; 
  int begin2 = mid + 1;
  int end1 = mid ;
  int end2 = end;
  int index = begin;

  while(begin1 <= end1 && begin2 <= end2)
  {
    if(a[begin1] < a[begin2])
    {
      tmp[index++] = a[begin1++];
    }
    else 
    {
      tmp[index++] = a[begin2++];
    }
  }
    while(begin1 <= end1)
    {
      tmp[index++] = a[begin1++];
    }
    while(begin2 <= end2)
    {
      tmp[index++] = a[begin2++];
    }
    memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin +1));//注意 每层递归只交换自己处理的数据
  }

  void Print(int*a, int n)
{
  int i =0;
  for( ; i<n; ++i )
  {
    printf("%d\t",a[i]);
  }
  printf("\n");
}


void  Test()
  {
    int a[] = {45,6,7,8,0,9,7,65};
    int n =sizeof(a) / sizeof(int);
    int tmp[n];
    Merge(a, 0, n-1,tmp);
    Print(a, n - 1);  
  }

int main()
{
  Test();
  return 0;
}


发布了90 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44030580/article/details/104327072