3.4插入排序
插入排序的基本思想是,经过i-1遍处理后,对象0i-1己排好序,第i遍处理的过程是将对象i插入对象0i-1之间的适当位置,使得对象0i又是排好序的序列。
void CSortDlg::OnInsertSort()
{
  CClientDC dc(this);
  dc.SetBkColor(RGB(180, 180, 180));
  int objectName[SORT_OBJECT_NUM]; //每个位置对应的成员名
   //初始化每个位置对应的成员
  for (int i = 0; i < SORT_OBJECT_NUM; i++)
  {
    objectName[i] = i;
  }
  //插入排序
  for (i = 1; i < SORT_OBJECT_NUM; i++)
  {
    int iPos = i - 1;
    int iName = objectName[i];
    CString strName, strNum;
 
 
    while (iPos >= 0 && sortObject[iName].iNumber <
      sortObject[objectName[iPos]].iNumber)
    {
      objectName[iPos + 1] = objectName[iPos];
      sortObject[objectName[iPos]].iSeq += 1;
      //显示iPos+1位置的对象
      strName.Format("%d", objectName[iPos + 1]);
      dc.TextOut(objectCoord[iPos + 1].x - 5, objectCoord[iPos + 1].y - 8,
        strName);
      if (sortObject[objectName[iPos + 1]].iNumber < 1000)
      {
        strNum.Format("  %d", sortObject[objectName[iPos + 1]].iNumber);
      }
      else
      {
        strNum.Format("%d", sortObject[objectName[iPos + 1]].iNumber);
      }
      dc.TextOut(objectCoord[iPos + 1].x - 15, objectCoord[iPos + 1].y - 30,
        strNum);
 
 
      iPos--;
 
 
      Sleep(1000);
    }
    objectName[iPos + 1] = iName;
    sortObject[objectName[iPos + 1]].iSeq = iPos + 1;
    //显示iPos+1位置的对象
    strName.Format("%d", objectName[iPos + 1]);
    dc.TextOut(objectCoord[iPos + 1].x - 5, objectCoord[iPos + 1].y - 8,
      strName);
    if (sortObject[objectName[iPos + 1]].iNumber < 1000)
    {
      strNum.Format("  %d", sortObject[objectName[iPos + 1]].iNumber);
    }
    else
    {
      strNum.Format("%d", sortObject[objectName[iPos + 1]].iNumber);
    }
    dc.TextOut(objectCoord[iPos + 1].x - 15, objectCoord[iPos + 1].y - 30,
      strNum);
 
 
    Sleep(1000);
  }
}
下面是我们追踪所得的一次插入排序的轨迹:
262 738 1699 4875 270 228 706 102 1787 1565
262 738 1699 4875 270 228 706 102 1787 1565
262 738 1699 4875 270 228 706 102 1787 1565
262 738 1699 4875 270 228 706 102 1787 1565
262 270 738 1699 4875 228 706 102 1787 1565
228 262 270 738 1699 4875 706 102 1787 1565
228 262 270 706 738 1699 4875 102 1787 1565
102 228 262 270 706 738 1699 4875 1787 1565
102 228 262 270 706 738 1699 1787 4875 1565
102 228 262 270 706 738 1565 1699 1787 4875
下面的动画(GIF格式)演示了插入排序的过程:


插入排序法的比较次数和交换次数与被排序数组的初始顺序情况有关:在最好、最坏、平均三种情况下,总的比较次数分别是(n-1)(n2+n-2)/4(n2+n)/2-1,总的交换次数是总比较次数减去n-1,它在最佳情况下为0,在最差及平均情况下为O(n2)。由此看出,在最坏情况下,插入排序法的交换次数与冒泡法、选择法一样糟糕,其执行时间和n2成正比。
D. L. Shell 发明了希尔(Shell)排序算法,该算法的基本思路是插入排序。希尔排序又称为缩小增量法,它的做法是先取定一个正整数d1 < n,把全部记录分成d1 个组,所有距离为d1倍数的记录放在一个组中,在个组内进行插入排序;然后取d2 < d1 ,重复上述分组和排序工作,直至取di = 1,即所有记录放在一个组中排序为止。大量的实验证明:当n在某个特定范围内时,希尔排序的比较次数和交换次数均为约n1.3
3.5快速排序
Tony Hoare1962年 首次提出了快速排序算法。对快速排序方法的研究表明,至今快速排序算法仍然是流传久远的最实用的排序算法。只在输入数据项有更详细的信息时,其它排序算法 才可能胜过快速排序算法。快速排序算法是利用分治技术的典型例子,随机分治策略是设计组合优化与计算几何一类问题有效算法的基础。分治策略分为三步: 
1)将问题分成若干大小基本相等的子问题;
2)独立地解决这些子问题;
3)将子问题归并成原问题的解。
快速排序的基本思路是:首先我们选择一个中间值middle(程序中我们可使用数组中间值),把比中间值小的放在其左边,比中间值大的放在其右边。由于这个排序算法较复杂,我们先给出其进行一次排序的程序框架(从各类数据结构教材中可得):
void QuickSort(int *pData, int left, int right)
{
  int i, j;
  int middle, iTemp;
  i = left;
  j = right;
  middle = pData[(left + right) / 2]; //求中间值
  do
  {
    while ((pData[i] < middle) && (i < right)) //从左扫描大于中值的数
    i++;
    while ((pData[j] > middle) && (j > left)) //从右扫描小于中值的数
    j--;
    if (i <= j) //找到了一对值
    {
      //交换
      iTemp = pData[i];
      pData[i] = pData[j];
      pData[j] = iTemp;
      i++;
      j--;
    }
  } while (i <= j) ; //如果两边扫描的下标交错,就停止(完成一次)
  //当左边部分有值(left<j),递归左半边
  if(left<j)
    QuickSort (pData,left,j);
  //当右边部分有值(right>i),递归右半边
  if(right>i)
    QuickSort (pData,i,right);
}
由此可见,上述排序算法中采用了递归策略。我们根据上述函数,重新书写一个与我们应用程序框架相符的函数,可动态在对话框客户区显示变化过程:
void QuickSort(int objectName[], CDC &dc, int left, int right)
{
      int i, j;
      int middle, iTemp;
      i = left;
      j = right;
      middle = sortObject[objectName[(left+right)/2]].iNumber; //求中间值
      do
      {
             while ((sortObject[objectName[i]].iNumber < middle) && ( i < right))
                   //从左扫描大于中值的数
                    i++;
             while ((sortObject[objectName[j]].iNumber > middle)&&(j > left))
                   //从右扫描小于中值的数
                    j--;
             if (i < j) //找到了一对值
             {
                    //交换成员名
                    iTemp = objectName[i];
                    objectName[i] = objectName[j];
                    objectName[j] = iTemp;
                    //交换序号
                    iTemp = sortObject[objectName[i]].iSeq;
                    sortObject[objectName[i]].iSeq = sortObject[objectName[j]].iSeq;
                    sortObject[objectName[j]].iSeq = iTemp;
                    //显示变化
                    CString strName;
                    CString strNum;                       
                    //显示位置j的对象
                    strName.Format("%d",objectName[j]);          
                    dc.TextOut(objectCoord[j].x-5,
                           objectCoord[j].y-8,
                           strName);
                    if(sortObject[objectName[j]].iNumber<1000)
                    {                         
                           strNum.Format("  %d",sortObject[objectName[j]].iNumber);
                    }
                    else
                    {
                           strNum.Format("%d",sortObject[objectName[j]].iNumber);
                    }
                    dc.TextOut(objectCoord[j].x-15,
                           objectCoord[j].y-30,
                           strNum);
                    //显示位置i的对象
                    strName.Format("%d",objectName[i]);          
                    dc.TextOut(objectCoord[i].x-5,
                           objectCoord[i].y-8,
                           strName);
                    if(sortObject[objectName[i]].iNumber<1000)
                    {
                           strNum.Format("  %d",sortObject[objectName[i]].iNumber);
                    }
                    else
                    {
                           strNum.Format("%d",sortObject[objectName[i]].iNumber);
                    }
                    dc.TextOut(objectCoord[i].x-15,
                           objectCoord[i].y-30,
                           strNum);
 
 
                    Sleep(1000);       
                   
                    i++;
                    j--;
             }
        else if (i==j)
             {
                    i++;
                    j--;
             }
      } while (i <= j) ; //如果两边扫描的下标交错,就停止(完成一次)
      //当左边部分有值(left<j),递归左半边
      if(left<j)
             QuickSort (objectName,dc,left,j);
      //当右边部分有值(right>i),递归右半边
      if(right>i)
             QuickSort (objectName,dc,i,right);
}
上述函数相对于原始的快速排序函数增加了两个参数:int objectName[], CDC &dc,前者用于记录某位置对应的对象名,后者用于记录设备上下文,以便显示排序的动态过程。下面给出了这个函数的调用:
void CSortDlg::OnQuickSort()
{   
      CClientDC dc(this) ;
      dc.SetBkColor(RGB(180,180,180));     
      int objectName[SORT_OBJECT_NUM]; //每个位置对应的成员名
   //初始化每个位置对应的成员
      for(int i=0;i<SORT_OBJECT_NUM;i++)
      {
             objectName[i] = i;
      }    
      QuickSort(objectName,dc,0,SORT_OBJECT_NUM-1);
}
下面是我们追踪所得的一次快速排序的轨迹:
154 1089 9859 564 554 4419 692 456 9359 9703
154 1089 456 564 554 4419 692 9859 9359 9703
154 1089 456 564 554 692 4419 9859 9359 9703
154 554 456 564 1089 692 4419 9859 9359 9703
154 456 554 564 1089 692 4419 9859 9359 9703
154 456 554 564 692 1089 4419 9859 9359 9703
154 456 554 564 692 1089 4419 9703 9359 9859
154 456 554 564 692 1089 4419 9359 9703 9859
下面的动画(GIF格式)演示了快速排序的过程:

对于n个成员,快速排序法的比较次数大约为n*logn 次,交换次数大约为(n*logn)/6次。如果n100,冒泡法需要进行4950 次比较,而快速排序法仅需要200 次,快速排序法的效率的确很高。快速排序法的性能与中间值的选定关系密切,如果每一次选择的中间值都是最大值(或最小值),该算法的速度就会大大下降。快速排序算法最坏情况下的时间复杂度为O(n2),而平均时间复杂度为O(n*logn)
4.总结
冒泡排序、选择排序和插入排序等的时间复杂性是O(n2),快速排序、堆排序和归并排序等高级排序算法的时间复杂度为O(n*logn),因此后者的排序效率较高。但这并不意味着总是要使用高级排序算法,下面给出各种排序算法选用的一般原则:
1)当数据量不大时选插入或选择排序,不要用冒泡排序;
事实上,冒泡排序虽然简单,但的确是最不宜采用的排序算法,因为其性能表现是最差的。
2)当数据量大而又注重时间复杂度时选快速或堆排序等;
堆排序的设计思路是:定义堆为一个键值序列{k1 ,k2 ,,kn},它具有如下特性:ki < = k2iki < = k2i + 1 (i = 1 ,2 ,, [ n/ 2 ]) 。对一组待排序的键值,首先把它们按堆的定义排列成一个序列(称为初建堆),这就找到了最小键值。然后将最小的键值取出,用剩下的键值再重新建堆,便得到次小键值。如此反复进行,直到把全部键值排好序为止。
由于快速排序和堆排序算法较复杂,因此在待排序的数据量较小时反而比插入和选择排序慢;
3)当要在已排序数据上增加新数据时选插入排序。
当原有数据本身就有序时,插入排序的性能非常好。在已排序数据上增加新数据时,时间复杂性仅为O(n)