【笔记12】打好基础——C与python分别实现七种排序

1 篇文章 0 订阅
本文记录学习七种排序(内排序)的代码,分别用C和python实现,必要时加图解释。这就是不好好学数据结构的后果
摘要由CSDN通过智能技术生成


本文记录学习七种排序(内排序)的代码,分别用C和python实现,必要时加图解释。 这就是不好好学数据结构的后果

排序

内排序一览表

在这里插入图片描述

C语言准备

利用结构体传输这个线性表。
实现定义:

#include<stdio.h>
#include<time.h>
#define MAXSIZE 10

typedef struct // 线性表存储
{
	int r[MAXSIZE];
	int length;
}SqList;

void swap(SqList *L, int i, int j) // 交换函数
{
    int temp=L->r[i];
    L->r[i]=L->r[j];
    L->r[j]=temp;
}

void ListPrint(SqList *L, int type) // 打印该列表
{
	int i;
	printf("[",type);
	for(i=1;i<L->length;i++)
	{
		printf("%d,",L->r[i]);
	}
	printf("\b]\n");
}

void main()
{
	SqList alist={
		{44987,9,1,5,8,3,7,4,6,2},
		9
	};
	InsertSort(&alist); // 直接插入
	ShellSort(&alist); // 希尔
	
	BubbleSort(&alist); //冒泡
	HeapSort(&alist); // 堆
	
	SecletionSort(&alist); // 简单选择
	QuickSort(&alist); // 快排

	MergingSort(&alist); // 归并
}

python准备

if __name__ == '__main__':
    alist = [44987,9,1,8,4,6,7,3,5,2]
    InsertionSort(alist)
    ShellSort(alist)

    BubbleSort(alist)
    HeapSort(alist)

    SelectionSort(alist)
    QuickSort(alist)

    MergingSort(alist)

1 插入排序

1.1 直接插入排序

要点:将数组第一个元素看作已经排好的序列,第二个到最后一个元素依次往这个序列里插,只要左边 < 该元素 < 右边

C version

void InsertSort(SqList *L)
{
	int i,j;
	for(i=2;i<=L->length;i++) // 假设r[1]为拍好的序列,循环至最后一位
	{
		if(L->r[i]<L->r[i-1]) // 如果该位比左边小,执行以下,将小数移至左边
		{
			L->r[0]=L->r[i]; // 利用r[0]存储r[i]的值
			for(j=i-1;L->r[j] > L->r[0];j--) // 从i-1向左找一个可以插入r[i]的值,这个r[j]需要大于r[i]
			{
				L->r[j+1]=L->r[j]; // 将这个比r[i]大的值向右移一位,此时j与j+1都相等,可以看作将j空出来了
			}
			L->r[j+1]=L->r[0]; // 此时j的值小于等于r[i]=r[0],插入右边
		}
	}
}

Python version

def InsertionSort(blist):
    length = len(blist) - 1
    for i in range(2, length + 1):
        if blist[i] < blist[i - 1]:
            temp = blist[i]
            j = i - 1
            while (j >= 0) & (blist[j] > temp):
                blist[j + 1] = blist[j]
                j -= 1
            blist[j + 1] = temp
    print blist[1:]

1.2 希尔排序

要点:先分组,每个组利用插入排序先排好序,每个组排好之后整体插入排序。
优点:先使得整个序列基本有序,大的数集中在后面,小的数集中在前面,在插入排序的时候就不需要数据过大的移动。
难点:怎么分组效率最高?基本有序 ≠ 局部有序,如果是顺序选取组,很有可能组内达到有序,但整体效率还是很低,如图:

44987
9
1
5
8
3
7
4
6
2

按三个为一组顺序分组,第三组的2插入左边排好的序列中,还是需要经过6次比较,性能并没有很大的提升。

44987
9
1
5
8
3
7
4
6
2

但如果我们按下标一定的增量分组,则可以达到整个数组的基本有序。根据一个增量序列 { i n c r e m e n t 1 , i n c r e m e n t 2 , . . . , 1 } \{increment1, increment2, ..., 1\} {increment1,increment2,...,1}来分组排序,当增量为1时则是一次直接插入排序。数组长度计算出最大增量(最大增量必须小于数组长度),这里我们最大增量选 l e n g t h / 2 length/2 length/2,每次将增量变为之前的一半。即增量序列为 { 5 , 2 , 1 } \{5,2,1\} {5,2,1}

在这里插入图片描述
可以看到在第一次分组时,r[5]没有进行排序,降低了效率。我们看另一种增列序列 { 4 , 2 , 1 } \{4,2,1\} {4,2,1},所有的元素在每一次分组都参与排序了,值得注意的是,我们的i是从increment+1开始循环,相当于把 i-increment看作是固定的,往左边插入元素。
以increment=2为例:
i=3时r[3]与r[1]比较,往前追溯发现没有同组的(1下标减去增量后小于0),i++;
i=4时r[4]与r[2]比较,往前追溯发现没有同组的(2下标减去增量后小于0),i++;
i=5时r[5]与r[3]比较,往前追溯同组的为r[1](3下标减去增量后等于1),将r[5]往该排好的序列插,完成后i++;
以此类推。
在这里插入图片描述
可以看出代码变动并不是很大,只是多加了一个循环,在插入排序的循环条件多加了j>0。这里可以学到很多编程技巧。

C version

void InsertSort(Shell *L)
{
	int i,j;
	int increment=L->hength;
	do
	{
		increment=increment/2;
		for(i=increment+1;i<=L->length;i++) //从第increment+1位开始,循环至最后一位
		{
			if(L->r[i]<L->r[i-increment]) //如果该位在组内比左边小,执行以下,将小数插至左边
			{
				L->r[0]=L->r[i]; //利用r[0]存储r[i]的值
				for(j=i-increment;L->r[j] > L->r[0] && j>0;j-=increment) //从i-1向左找一个可以插入r[i]的值,这个r[j]需要大于r[i]
				{
					L->r[j+increment]=L->r[j]; //将这个比r[i]大的值向右移一位
				}
				L->r[j+increment]=L->r[0]; //此时j的值小于等于r[i]=r[0],插入右边
			}
		}
	}
	while(increment>1)
}

Python version

def ShellSort(blist):
    length = len(blist) - 1
    increment = length / 2

    while increment:
        for i in range(increment + 1, length + 1):
            if blist[i]< blist[i - increment]:
                temp = blist[i]
                j = i - increment
                while (j >= 0) & (blist[j] > temp):
                    blist[j + increment] = blist[j]
                    j -= increment
                blist[j + increment] = temp
        increment = increment / 2
    print blist[1:]

2 交换排序

2.1 冒泡排序

C version

冒泡排序优化前后一共有三个常见版本,第一个版本的思路不是严格意义的冒泡排序,而是普通的交换排序,因为不是两两比较相邻元素完成交换,思路是把与当前元素比较最小的放到前面。。

void ExchangeSort(SqList *L)
{
	int i,j;
	for(i=1;i<L->length;i++) // 从1到length-1
	{
		for(j=i+1;j<=L->length;j++) // 从i+1到length,i与j两两比较,区别于冒泡的相邻比较
		{
			if(L->r[j]<L->r[i])
			{
				swap(L,i,j);
			}
		}
	}
}

第二个版本则是我们正宗的冒泡排序,从前往后扫描,依次填入当前序列最小的数值,这个找最小数的过程是从数组尾往前循环,两两比较,直至i+1,看起来就像最小的数字往数组头浮。

void BubbleSort(SqList *L)
{
	int i,j;
	for(i=1;i<L->length;i++) // 从1到length-1
	{
		for(j=L->length-1;j>=i;j--) // 从数组尾开始,因为要把小的数放到前面,交换的方向就是“浮”
		{
			//这里注意从length-1开始,我们需要将剩下最小的放到j=i处,如果从length开始则条件为j>i,比较为L->r[j]<L->r[j-1],交换为swap(L,j,j+1)
			if(L->r[j]<L->r[j+1])
			{
				swap(L,j,j+1)
			}
		}
	}
}

第三个版本是优化版本,目的是在数组在完成某次交换后已经有序,但i还是会继续循环,并对每个i寻找最小的值。如何告诉程序目前已经有序了?思路是设一个标记,当剩下的两两比较都是前小于后(完全有序)则告诉循环该停止了。

void BubbleSortOptimize(SqList *L)
{
	int i,j;
	int Check=1;
	// C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false
	for(i=1;i<L->length && Check;i++) // 从1到length-1,循环条件是i小于length且Check不为0
	{
		Check=0; // 如果退出时还是0,则判断为false,循环结束
		for(j=L->length-1;j>=i;j--) 
		{
			if(L->r[j]<L->r[j+1])
			{
				swap(L,j,j+1)
				Check=1;
			}
		}
	}
}

Python version

def BubbleSort(blist):
    length = len(blist) - 1
    check = True
    for i in range(1, length):
        check = False
        for j in range(length - i):
            if blist[length - j] < blist[length - 1 - j]:
                blist[length - j], blist[length - 1 - j] = blist[length - 1 - j], blist[length - j]
                check = True
        if check == False:
            break
    print blist[1:]

2.2 快速排序

超级经典的排序算法,采用分治的思想,找一个数将数组分为两区,分区重复处理。
快排细分起来分为左右指针法、挖坑法、前后指针法,都是采用的递归实现;同时还可以使用非递归实现,将递归变为迭代。

临时查找的一篇博客:快速排序算法—左右指针法,挖坑法,前后指针法,递归和非递归

C version

int Partition(SqList *,int front,int back)
{
	int pivotkey;
	pivotvalue=L->r[front];
	while(front<back)
	{
		while(front<back && L->r[back]>=pivotvalue)
		{
			back--;
		}
		swap(L,front,back);
		while(front<back && L->r[front]<=pivotvalue)
		{
			front++;
		}
		swap(L,front,back);
	}
	return front;
}

void Qsort(SqList *,int front,int back)
{
	int i,pivot;
	if(front<back)
	{
		pivot=Partition(L,front,back);
		Qsort(L,front,pivot-1);
		Qsort(L,pivoit+1,back);
	}
}

void QuickSort(SqList *L)
{
	Qsort(L,1,L->length);
}

Python version

def QuickSort(blist):
    def Qsort(front, back):
        print blist[1:]
        while front < back:
            pivot = Partition(front, back)
            Qsort(front, pivot - 1)
            front = pivot + 1

    def Partition(front,back):
        pivot_value = blist[front]
        while front < back:
            while (front < back) & (blist[back] >= pivot_value):
                back -= 1
            blist[front], blist[back] = blist[back], blist[front]
            while (front < back) & (blist[front] <= pivot_value):
                front += 1
            blist[front], blist[back] = blist[back], blist[front]
        return front

    length = len(blist) - 1
    Qsort(1, length)
    print blist[1:]

3 选择排序

3.1 简单选择排序

简单选择排序的思路是对每一个i,在余下序列中找最小值,记录下标,一次交换。

C version

void SelectionSort()
{
	int i,j,min;
	for(i=1;i<L->length;i++)
	{
		min=i;
		for(j=i+1;j<=L->length;j++)
		{
			if(L->r[j]<L->r[min]) // 在余下序列中找比当前存储最小值还小的
			{
				min=j;
			}	
		}
		if(min!=i) // 经过查找后如果存在更小的,交换
		{
			swap(L,i,min);
		}
	}
}

python version

def SelectionSort(blist):
    length = len(blist) - 1
    for i in range(1,length):
        min_temp = i
        for j in range(i + 1, length + 1):
            if blist[j] <= blist[min_temp]:
                min_temp = j
        if min_temp != i:
            blist[i], blist[min_temp] = blist[min_temp], blist[i]
    print blist[1:]

3.2 堆排序

堆排序的思路分为两点:1. 把数组构造成一个大顶堆。2.将最大数和数组尾交换,调整余下的堆。

C version

void HeapAdjust(SqList *L,int front,int back)
{
	int i;
	L->r[0]=L->r[front];
	for(i=2*front;i<=back;i*=2) // front为根节点下标,2*front是左孩子
	{
		if(i<back && L->r[i]<L->r[i+1]) // 先检查当前下标是否节点,再看如果其右孩子比左孩子大,移到右孩子
		{
			++i;
		}
		if(L->r[i] <= L->r[0]) // 如果当前值不大于根节点,没有比较的意义
		{
			break;
		}
		L->r[front]=L->r[i]; // 把当前最大的r[i]放到头 ,看作位置空了 
		front=i; // 用front记录空的位置,检查该位置是否最大根,相当于把递归HeapAjust(L,i,length)变为迭代 
	}
	L->r[front]->L->r[0]; // 退出循环时front为空,且没有孩子
}

void Heap(SqList *l)
{
	int i;
	for(i=L->length/2;i>0;i--) // 把数组构造成一个最大堆
	{
		HeapAdjust(L,i,L->length);
	}
	for(i=L->length;i>1;i--) // 从最后一个数开始,换第一个最大数,直到根和其左孩子交换完毕
	{
		swap(L,1,i); // 每一次都是第一个数和当前数换(最后一个)
		HeapAdjust(L,1,i-1); // 剩下的树为1到length-1
	}
}

Python version

def HeapSort(blist):
    def HeapAdjust(front, back):
        temp = blist[front]
        i = 2 * front
        while i <= back:
            if (i < back) & (blist[i] < blist[i + 1]):
                i += 1
            if blist[i] <= temp:
                break
            blist[front] = blist[i]
            front = i
            i = 2 * front
        blist[i / 2] = temp

    length = len(blist) - 1
    for i in range(1, (length / 2) + 1)[::-1]:
        HeapAdjust(i, length)
    i = length
    while i > 1:
        blist[1], blist[i] = blist[i], blist[1]
        i -= 1
        HeapAdjust(1,i)
    print blist[1:]

4 归并排序

感觉归并排序思路看起来简单,但是实现起来比较难,思考递归的时候很容易陷入递归迷宫,就像这样……
6在这里插入图片描述

void Merge(int STree[],int NTree[],int front,int pivot,int back)
{
	int i,j;
	for(i=front,j=pivot+1;i<=pivot && j<=back;front++)
	{
		if(STree[i]<STree[j])
		{
			NTree[front]=STree[i++];
		}
		else
		{
			NTree[front]=STree[j++];
		}
	}
	if(i<=pivot) //这里是个坑,必须是小于等于,只要ij指针还在其中一个组里,就说明还有值未放入。 
	{
		while(i<=pivot) 
		{
			NTree[front++]=STree[i++];
		}
	}
	if(j<=back)
	{
		while(j<=back) 
		{
			NTree[front++]=STree[j++];
		}
	}
}

void Msort(int OTree[],int NTree[],int front,int back)
{
	// OTree, NTree, STree分别是oldtree, newtree, savetree
	int pivot;
	int STree[MAXSIZE+1];
	if(front==back)
		NTree[front]=OTree[front];
	else
	{
		pivot=(front+back)/2;
		Msort(OTree,STree,front,pivot);
		Msort(OTree,STree,pivot+1,back);
		Merge(STree,NTree,front,pivot,back);
	}
}

void MergeSort(SqList *L)
{
	Msort(L->r,L->r,1,L->length); 
	ListPrint(L,1);
}

打印了一下过程,可以看到每一次调用Msort,NTree和STree都会重置,第一次Msort接收的OTree和NTree都是L->r,前6次之所以进入Msort时是左枝一直在递归,Msort没有完成时不会调用Merge,当发现最后两个叶子节点(第5,6次),将两个叶子节点存在STree里,返回时再调用第一次Merge。
我这里打印的是刚进入Msort的变量,实际上当 front: 1 back: 1 进入Msort的之后,接受的STree空间一直是之前的,在判断front=back之后,才把OTree中front位的值赋给了STree的front位,退出递归之后下一步为Merge,这里传的STree地址并没有变。
结合栈的知识可能更好理解,但我现在也被自己绕晕了。

MSort: 1
Merge: 0
front: 9 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [9,1,5,8,3,7,4,6,2]
STree: [0,268501009,0,4203696,0,-2063292794,32763,12191544,0]

MSort: 2
Merge: 0
front: 9 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,268501009,0,4203696,0,-2063292794,32763,12191544,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 3
Merge: 0
front: 9 back: 5
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 4
Merge: 0
front: 9 back: 1
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 5
Merge: 0
front: 9 back: 9
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 6
Merge: 0
front: 1 back: 1
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [9,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 7
Merge: 1
front: 5 back: 5
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,0,12194608,0,0,0,12194608,0]
STree: [9,1,0,12194608,0,0,0,12194608,0]

MSort: 8
Merge: 2
front: 8 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,12194608,0,0,0,12194608,0]
STree: [1,9,5,12194608,0,0,0,12194608,0]

MSort: 9
Merge: 2
front: 8 back: 8
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,5,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 10
Merge: 2
front: 3 back: 3
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,9,5,8,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 11
Merge: 4
front: 7 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,3,5,8,9,-2063292794,32763,12191544,0]
STree: [1,5,9,3,8,0,0,12194608,0]

MSort: 12
Merge: 4
front: 7 back: 4
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,3,8,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 13
Merge: 4
front: 7 back: 7
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,0,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 14
Merge: 4
front: 4 back: 4
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,0,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 15
Merge: 5
front: 6 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [1,5,9,3,8,4,7,12194608,0]
STree: [0,38,0,12194608,0,7,4,12194608,0]

MSort: 16
Merge: 5
front: 6 back: 6
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,4,12194608,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]

MSort: 17
Merge: 5
front: 2 back: 2
OTree: [9,1,5,8,3,7,4,6,2]
NTree: [0,38,0,12194608,0,7,4,6,0]
STree: [0,38,0,12194608,0,0,0,12194608,0]
17,8[1]
Msort: 17, Merge: 8

Python version

def MergeSort(blist):
    def Msort(front, back):
        if front == back:
            return [blist[front]]
        else:
            pivot = (front + back) / 2
            left = Msort(front, pivot)
            right = Msort(pivot + 1, back)
            return Merge(left, right)

    def Merge(left, right):
        i, j = 0, 0
        s_tree = []
        while i < len(left) and j < len(right):
            if left[i] < right[j]:
                s_tree.append(left[i])
                i += 1
            else:
                s_tree.append(right[j])
                j += 1
        if i < len(left):
            s_tree.extend(left[i:])
        if j < len(right):
            s_tree.extend(right[j:])
        return s_tree


    length = len(blist) - 1
    alist = Msort(1, length)
    print alist
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值