数据结构和算法(C语言)

数据结构和算法

1 概述

  • 定义

    数据结构 = 个体的存储 +个体关系的存储 (研究数据的存储)

    算法 = 对存储数据的操作 (对数据的操作)

  • 衡量算法的标准

    1. 时间复杂度

      大概程序要执行的次数,而非执行的时间

    2. 空间复杂度

      算法执行过程中大概所占用的最大内存

    3. 难易程度

    4. 健壮性

  • 预备知识

    • 指针

      指针就是地址 地址就是指针

      指针变量是存放内存单元地址的变量

      指针的本质是一个操作受限的非负整数

      一个字节就是一个地址,比方说一个double型的变量有8个字节,那么指针当中存了8个地址吗?不是,一般是存了这8个地址当中的首地址。

    • 地址

      地址就是内存单元的编号

      从0开始的非负整数

      范围:0——FFFFFFFF【0-4G-1】

    • 结构体

    • 跨函数使用内存——malloc

2 线性结构【把所有的节点用一根直线穿起来】

2.1 连续存储[数组]

  • 什么叫数组

    元素类型相同,大小相同

  • 优缺点

    • 优点

      • 存取速度很快
    • 缺点

      • 事先必须知道数组的长度
      • 插入删除元素很慢
      • 空间通常是有限制的
      • 需要大块连续的内存块

2.2 离散存储[链表]

  • 定义

    n个节点离散分配

    彼此通过指针相连

    每个节点只有一个前驱节点,后续节点

    首节点没有前驱节点,尾节点没有后续节点

  • 专业术语:

    • 首节点:第一个有效节点

    • 尾节点:最后一个有效节点

    • 头节点:

      头节点的数据类型和首节点类型一样

      第一个有效节点之前的那个节点

      头节点并不存放有效数据

      加头节点的目的主要是为了方便对链表的操作

    • 头指针:指向头结点的指针变量

    • 尾指针:指向尾节点的指针变量

  • 如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数

    只需要一个参数:头指针,因为我们通过头指针可以推算出链表的其它所有参数

  • 分类

    • 单链表

    • 双链表:

      每一个节点有两个指针域

    • 循环链表:

      能通过任何一个节点找到其他所有的节点

    • 非循环链表

  • 算法

    • 遍历

    • 查找

    • 清空

    • 销毁

    • 求长度

    • 排序

    • 删除节点

    • 插入节点

      狭义的算法是与数据的存储方式密切相关

      广义的算法是与数据的存储方式无关

      泛型:

      ​ 利用某种技术达到的效果就是:不同的存数方式,执行的操作是一样的

  • 优缺点

    • 优点

      • 空间没有限制

      • 插入删除元素很快

    • 缺点

      • 存取速度很慢
  • 代码实现

    # include <stdio.h>
    # include <malloc.h>
    # include <stdlib.h>
    
    typedef struct Node
    {
    	int data; //数据域
    	struct Node * pNext; //指针域
    }NODE, *PNODE; //NODE等价于struct Node    PNODE等价于struct Node *
    
    //函数声明
    PNODE create_list(void);
    void traverse_list(PNODE pHead);
    bool is_empty(PNODE pHead);
    int length_list(PNODE);
    bool insert_list(PNODE, int, int);  //在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
    bool delete_list(PNODE, int, int *);
    void sort_list(PNODE);
    
    
    int main(void)
    {
    	PNODE pHead = NULL; //等价于 struct Node * pHead = NULL;
    	int val;
    
    	pHead = create_list();  //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHead
    	traverse_list(pHead);
    	
    	//insert_list(pHead, -4, 33);
    	if ( delete_list(pHead, 4, &val) )
    	{
    		printf("删除成功,您删除的元素是: %d\n", val);
    	}
    	else
    	{
    		printf("删除失败!您删除的元素不存在!\n");
    	}
    
    	traverse_list(pHead);
    	
    	//int len = length_list(pHead);
    	//printf("链表的长度是%d\n", len);
    
    	//sort_list(pHead);
    	//traverse_list(pHead);
    	
    /*	if ( is_empty(pHead) )
    		printf("链表为空!\n");
    	else
    		printf("链表不空!\n");
    */
    	return 0;
    }
    
    PNODE create_list(void)
    {
    	int len;  //用来存放有效节点的个数
    	int i;
    	int val; //用来临时存放用户输入的结点的值
    
    	//分配了一个不存放有效数据的头结点
    	PNODE pHead = (PNODE)malloc(sizeof(NODE));
    	if (NULL == pHead)
    	{
    		printf("分配失败, 程序终止!\n");
    		exit(-1);
    	}
    	PNODE pTail = pHead;
    	pTail->pNext = NULL;
    
    	printf("请输入您需要生成的链表节点的个数: len = ");
    	scanf("%d", &len);
    	
    	for (i=0; i<len; ++i)
    	{
    		printf("请输入第%d个节点的值: ", i+1);
    		scanf("%d", &val);
    		
    		PNODE pNew = (PNODE)malloc(sizeof(NODE));
    		if (NULL == pNew)
    		{
    			printf("分配失败, 程序终止!\n");
    			exit(-1);
    		}
    		pNew->data = val;
    		pTail->pNext = pNew;
    		pNew->pNext = NULL;
    		pTail = pNew;
    	}
    	
    	return pHead;
    }
    
    void traverse_list(PNODE pHead)
    {
    	PNODE p = pHead->pNext;
    
    	while (NULL != p)
    	{
    		printf("%d  ", p->data);
    		p = p->pNext;
    	}
    	printf("\n");
    	
    	return;
    }
    
    bool is_empty(PNODE pHead)
    {
    	if (NULL == pHead->pNext)
    		return true;
    	else
    		return false;
    }
    
    int length_list(PNODE pHead)
    {
    	PNODE p = pHead->pNext;
    	int len = 0;
    
    	while (NULL != p)
    	{
    		++len;
    		p = p->pNext;
    	}
    
    	return len;
    }
    
    void sort_list(PNODE pHead)
    {
    	int i, j, t;
    	int len = length_list(pHead);
    	PNODE p, q;
    	
    	for (i=0,p=pHead->pNext; i<len-1; ++i,p=p->pNext)
    	{
    		for (j=i+1,q=p->pNext; j<len; ++j,q=q->pNext)
    		{
    			if (p->data > q->data)  //类似于数组中的:  a[i] > a[j]
    			{
    				t = p->data;//类似于数组中的:  t = a[i];
    				p->data = q->data; //类似于数组中的:  a[i] = a[j];
    				q->data = t; //类似于数组中的:  a[j] = t;
    			}
    		}
    	}
    
    	return;
    }
    
    //在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
    bool insert_list(PNODE pHead, int pos, int val)
    {
    	int i = 0;
    	PNODE p = pHead;
    
    	while (NULL!=p && i<pos-1)
    	{
    		p = p->pNext;
    		++i;
    	}
    
    	if (i>pos-1 || NULL==p)
    		return false;
    
    	PNODE pNew = (PNODE)malloc(sizeof(NODE));
    	if (NULL == pNew)
    	{
    		printf("动态分配内存失败!\n");
    		exit(-1);
    	}
    	pNew->data = val;
    	PNODE q = p->pNext;
    	p->pNext = pNew;
    	pNew->pNext = q;
    
    	return true;
    }
    
    
    bool delete_list(PNODE pHead, int pos, int * pVal)
    {
    	int i = 0;
    	PNODE p = pHead;
    
    	while (NULL!=p->pNext && i<pos-1)
    	{
    		p = p->pNext;
    		++i;
    	}
    
    	if (i>pos-1 || NULL==p->pNext)
    		return false;
    	
    	PNODE q = p->pNext;
    	*pVal = q->data;
    
    	//删除p节点后面的结点
    	p->pNext = p->pNext->pNext;
    	free(q);
    	q = NULL;
    	
    	return true;
    
    }
    

2.3 线性结构的常见应用——栈

  • 定义

    • 一种可以实现“先进先出”的存储结构,类似于箱子
  • 分类

    • 静态栈
    • 动态栈
  • 算法

    • 出栈

    • 压栈

    • 遍历

      # include <stdio.h>
      # include <malloc.h>
      # include <stdlib.h>
      
      typedef struct Node
      {
      	int data;
      	struct Node * pNext;
      }NODE, * PNODE;
      
      typedef struct Stack
      {
      	PNODE pTop;
      	PNODE pBottom;
      }STACK, * PSTACK;  //PSTACK 等价于 struct STACK *
      
      void init(PSTACK);
      void push(PSTACK, int );
      void traverse(PSTACK);
      bool pop(PSTACK, int *);
      void clear(PSTACK pS);
      
      int main(void)
      {
      	STACK S;  //STACK 等价于 struct Stack
      	int val;
      
      	init(&S);  //目的是造出一个空栈
      	push(&S, 1); //压栈
      	push(&S, 2);
      	push(&S, 3);
      	push(&S, 4);
      	push(&S, 5);
      	push(&S, 6);
      	traverse(&S); //遍历输出
      	
      	clear(&S);
      	//traverse(&S); //遍历输出
      
      	if ( pop(&S, &val) )
      	{
      		printf("出栈成功,出栈的元素是%d\n", val);
      	}
      	else
      	{
      		printf("出栈失败!\n");
      	}
      
      	traverse(&S); //遍历输出
      
      	return 0;
      }
      
      void init(PSTACK pS)
      {
      	pS->pTop = (PNODE)malloc(sizeof(NODE));
      	if (NULL == pS->pTop)
      	{
      		printf("动态内存分配失败!\n");
      		exit(-1);
      	}
      	else
      	{
      		pS->pBottom = pS->pTop;
      		pS->pTop->pNext = NULL; //pS->Bottom->pNext = NULL;
      	}
      }
      
      void push(PSTACK pS, int val)
      {
      	PNODE pNew = (PNODE)malloc(sizeof(NODE));
      	
      	pNew->data = val;
      	pNew->pNext = pS->pTop; //pS->Top不能改成pS->Bottom
      	pS->pTop = pNew;
      
      	return;
      }
      
      void traverse(PSTACK pS)
      {
      	PNODE p = pS->pTop;
      
      	while (p != pS->pBottom)
      	{
      		printf("%d  ", p->data);
      		p = p->pNext;
      	}
      	printf("\n");
      
      	return;
      }
      
      bool empty(PSTACK pS)
      {
      	if (pS->pTop == pS->pBottom)
      		return true;
      	else
      		return false;
      }
      
      //把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,如果出栈失败,返回false,否则返回true
      bool pop(PSTACK pS, int * pVal)
      {
      	if ( empty(pS) ) //pS本身存放的就是S的地址
      	{
      		return false;
      	}
      	else
      	{
      		PNODE r = pS->pTop;
      		*pVal = r->data;
      		pS->pTop = r->pNext;
      		free(r);
      		r = NULL;
      
      		return true;
      	}
      }
      
      //clear清空
      void clear(PSTACK pS)
      {
      	if (empty(pS))
      	{
      		return;
      	}
      	else
      	{
      		PNODE p = pS->pTop;
      		PNODE q = NULL;
      
      		while (p != pS->pBottom)
      		{
      			q = p->pNext;
      			free(p);
      			p = q;
      		}
      		pS->pTop = pS->pBottom;
      	}
      }
      
  • 应用

    • 函数调用
    • 中断
    • 表达式求值
    • 内存分配
    • 缓冲处理
    • 迷宫

2.3 线性结构的常见应用——队列

  • 定义:

    一种可以实现“先进先出”的存储结构

  • 分类

    • 链式队列——用链表实现

    • 静态队列——用数组实现

      静态队列通常都必须是循环队列

    • 循环队列的讲解:

      1. 静态队列为什么必须是循环队列

        空间利用率

      2. 循环队列需要几个参数来确定

        需要两个参数来确定

      3. 循环队列各个参数的含义

        • 队列初始化:

          font和rear的值都是0

        • 队列非空:

          font代表的是队列的第一个元素

          rear代表的是队列的最后一个有效元素的下一个元素

        • 队列空

          font和rear的值相等,但不一定是0

      4. 循环队列入队伪算法讲解

        两步完成:

        1. 将值存入r所代表的位置
        2. r = (r+1) % 数组的长度
      5. 循环队列出队伪算法讲解

        f = (f+1) % 数组的长度

      6. 如何判断循环队列是否为空

        如果front与rear的值相等,则该队列一定为空

      7. 如何判断循环队列是否已满

        少用一个,如果r和f的值紧挨着,则队列已满

        if ((r + 1) % 数组长度 == f)

  • 队列的具体应用:

    所有和时间有关的操作都与队列有关

  • 代码示例

    # include <stdio.h>
    # include <malloc.h>
    
    typedef struct Queue
    {
    	int * pBase;
    	int front;
    	int rear;
    }QUEUE;  
    
    void init(QUEUE *);
    bool en_queue(QUEUE *, int val);  //入队
    void traverse_queue(QUEUE *);
    bool full_queue(QUEUE *);
    bool out_queue(QUEUE *, int *);
    bool emput_queue(QUEUE *);
    
    int main(void)
    {
    	QUEUE Q;
    	int val;
    
    	init(&Q);
    	en_queue(&Q, 1);
    	en_queue(&Q, 2);
    	en_queue(&Q, 3);
    	en_queue(&Q, 4);
    	en_queue(&Q, 5);
    	en_queue(&Q, 6);
    	en_queue(&Q, 7);
    	en_queue(&Q, 8);
    
    	traverse_queue(&Q);
    
    	if ( out_queue(&Q, &val) )
    	{
    		printf("出队成功,队列出队的元素是: %d\n", val);
    	}
    	else
    	{
    		printf("出队失败!\n");
    	}
    	traverse_queue(&Q);
    
    	return 0;
    }
    
    void init(QUEUE *pQ)
    {
    	pQ->pBase = (int *)malloc(sizeof(int) * 6);
    	pQ->front = 0;
    	pQ->rear = 0;
    }
    
    bool full_queue(QUEUE * pQ)
    {
    	if ( (pQ->rear + 1) % 6 == pQ->front  )
    		return true;
    	else
    		return false;
    }
    
    bool en_queue(QUEUE * pQ, int val)
    {
    	if ( full_queue(pQ) )
    	{
    		return false;
    	}
    	else
    	{
    		pQ->pBase[pQ->rear] = val;
    		pQ->rear = (pQ->rear+1) % 6;
    
    		return true;
    	}
    }
    
    void traverse_queue(QUEUE * pQ)
    {
    	int i = pQ->front;
    
    	while (i != pQ->rear)
    	{
    		printf("%d  ", pQ->pBase[i]);
    		i = (i+1) % 6;
    	}
    	printf("\n");
    
    	return;
    }
    
    bool emput_queue(QUEUE * pQ)
    {
    	if ( pQ->front == pQ->rear )
    		return true;
    	else
    		return false;
    }
    
    bool out_queue(QUEUE * pQ, int * pVal)
    {
    	if ( emput_queue(pQ) )
    	{
    		return false;
    	}
    	else
    	{
    		*pVal = pQ->pBase[pQ->front];
    		pQ->front = (pQ->front+1) % 6;
    
    		return true;
    	}
    }
    

2.4 递归

  • 定义

    一个函数自己直接或间接调用自己

  • 前提条件

    1. 递归必须得有一个明确的终止条件
    2. 该函数所处理的数据规模必须在递减
    3. 这个转化必须是可解的
  • 循环和递归

    • 递归:
      • 易于理解
      • 速度慢
      • 存储空间大
    • 循环:
      • 不易理解
      • 速度快
      • 存储空间小
  • 举例

    1. 求阶乘

    2. 1+2+3+4+…100的和

    3. 汉诺塔

      # include <stdio.h>
      /*
       * n是盘子的数量
       * A代表盘子所在的柱子
       * B代表需要借助的柱子
       * C代表盘子所去的目的地
       */
      void hannuota(int n, char A, char B, char C)
      {
      /*
      	如果是1个盘子
      		直接将A柱子上的盘子从A移到C
      	否则
      		先将A柱子上的n-1个盘子借助C移到B
      		直接将A柱子上的盘子从A移到C
      		最后将B柱子上的n-1个盘子借助A移到C
      */
      	if (1 == n)
      	{
      		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
      	}
      	else
      	{
      		hannuota(n-1, A, C, B);
      		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
      		hannuota(n-1, B, A, C);
      	}
      }
      
      int main(void)
      {
      	char ch1 = 'A';
      	char ch2 = 'B';
      	char ch3 = 'C';
      	int n;
      
      	printf("请输入要移动盘子的个数: ");
      	scanf("%d", &n);
      
      	hannuota(n, 'A', 'B', 'C');
      
      
      	return 0;
      }
      
    4. 走迷宫

  • 递归的应用

    • 数和森林就是以递归的方式定义的
    • 数和图的很多算法都是以递归来实现的
    • 很多数学公式就是以递归的方式定义的

3 非线性结构

3.1 树

  • 定义

    • 专业定义
      1. 有且只有一个称为根的节点
      2. 有若干个互不相交的子树,这些子树本身也是一棵树
    • 通俗的定义
      1. 树是由节点和边组成
      2. 每个节点只有一个父节点但可以有多个子节点
      3. 但是有一个节点例外,该节点没有父节点,此节点称为根节点
    • 专业术语
      • 节点 父节点
      • 子孙
      • 深度:从根节点到最底层节点的层数
      • 叶子节点:没有子节点的节点
      • 非叶子节点
      • 度:子节点的个数称为度
  • 分类

    • 一般树
      • 任意一个节点的子节点的个数都不受限制
    • 二叉树
      • 任意一个节点的子节点个数最多两个,且子节点的位置不可更改
      • 二叉树的分类
        • 二叉搜索树:有序树
        • 满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树
        • 完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树
    • 森林
      • n个互不相交的树的集合
  • 二叉树的存储

    • 连续存储【需要转化为完全二叉树】
      • 优点:查找某个节点的父节点和子节点(也包括判断有没有子节点)
      • 缺点:耗用内存空间过大
    • 链式存储
  • 森林的存储

    先把森林转化为二叉树,再存储二叉树

  • 二叉树操作

    • 遍历

      • 先序遍历【先访问根节点】

        先访问根节点,再先序访问左子树,再先序访问右子树

      • 中序遍历【中间访问根节点】

        中序遍历左子树,再访问根节点,再中序遍历右子树

      • 后序遍历【最后访问根节点 】

        先后序遍历左子树,再后序遍历右子树,再访问根节点

  • 已知两种遍历序列求原始二叉树

    • 通过先序和中序

    • 通过中序和后序

  • 应用

    • 树是数据库中数据组织一种重要形式
    • 操作系统父子进程的关系本身就是一棵树
    • 面向对象语言中类的继承关系本身就是一棵树
    • 赫夫曼树
  • 插讲一个知识点

    struct BTnode* function()
    {
    	//肯定是动态创建的二叉树,静态分配会被释放掉
        int i;//静态分配
        char str[100];//静态分配
        struct BTnode *p = (struct BTnode *)malloc(sizeof(struct BTnode));//p是静态分配,malloc动态分配,静态分配的变量函数调用结束会被释放掉,所以在调用该函数时,应创建一个指针来指向动态分配好的空间
        return p;
    }
    
  • 代码示例

    # include <stdio.h>
    # include <malloc.h>
    
    struct BTNode
    {
    	char data;
    	struct BTNode * pLchild; //p是指针 L是左  child是孩子
    	struct BTNode * pRchild;
    };
    
    void PostTraverseBTree(struct BTNode * pT);
    struct BTNode * CreateBTree(void);
    void PreTraverseBTree(struct BTNode * pT);
    void InTraverseBTree(struct BTNode * pT);
    
    int main(void)
    {
    	struct BTNode * pT = CreateBTree();
    	
    //	PreTraverseBTree(pT);
    //	InTraverseBTree(pT);
    	PostTraverseBTree(pT);
    	
    	return 0;
    }
    
    void PostTraverseBTree(struct BTNode * pT)
    {
    	if (NULL != pT)
    	{
    		if (NULL != pT->pLchild)
    		{
    			PostTraverseBTree(pT->pLchild);
    		}	
    		if (NULL != pT->pRchild)
    		{
    				PostTraverseBTree(pT->pRchild);
    			//pT->pLchild可以代表整个左子树
    		}
    		printf("%c\n", pT->data);
    	}
    }
    
    void InTraverseBTree(struct BTNode * pT)
    {
    	if (NULL != pT)
    	{
    		if (NULL != pT->pLchild)
    		{
    			InTraverseBTree(pT->pLchild);
    		}
    		
    		printf("%c\n", pT->data);
    	
    		if (NULL != pT->pRchild)
    		{
    				InTraverseBTree(pT->pRchild);
    			//pT->pLchild可以代表整个左子树
    		}	
    	}
    }
    
    void PreTraverseBTree(struct BTNode * pT)
    {
    	if (NULL != pT)
    	{
    		printf("%c\n", pT->data);
    	
    		if (NULL != pT->pLchild)
    		{
    			PreTraverseBTree(pT->pLchild);
    		}
    		
    		if (NULL != pT->pRchild)
    		{
    				PreTraverseBTree(pT->pRchild);
    			//pT->pLchild可以代表整个左子树
    		}	
    	}	
    
    /*
    	伪算法
    	先访问根节点
    	再先序访问左子树
    	再先序访问右子树
    */
    }
    
    struct BTNode * CreateBTree(void)
    {
    	struct BTNode * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
    	struct BTNode * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
    	struct BTNode * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
    	struct BTNode * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
    	struct BTNode * pE = (struct BTNode *)malloc(sizeof(struct BTNode));
    
    	pA->data = 'A';
    	pB->data = 'B';
    	pC->data = 'C';
    	pD->data = 'D';
    	pE->data = 'E';
    
    	pA->pLchild = pB;
    	pA->pRchild = pC;
    	pB->pLchild = pB->pRchild = NULL;
    	pC->pLchild = pD;
    	pC->pRchild = NULL;
    	pD->pLchild = NULL;
    	pD->pRchild = pE;
    	pE->pLchild = pE->pRchild = NULL;
    
    	return pA;
    }
    

4 排序

4.1 冒泡排序

  • 原理:

    将一段序列的最大值(最小值)拿到最左边或者最右边的操作,使用循环重复操作,(每轮排序都会少一个最大值或最小值),当最后只剩下一个数据的时候整个序列就已经排好序了。

  • 代码实现

    /*
    直接使用两层循环去实现,外层循环主要作用是存放最大值或最小值的,内存循环的主要作用是找到发生冲突的元素,如果发生冲突就交换两个数据。当两层循环的结束的时候整个序列就自然排好序了。时间复杂度为O(n^2).
    */
    void bubSort(Ty* elems,int size) {
    	for(int i = 0; i < size -1; i++)
        {
    		for(int j = 0; j < size - i - 1; j++)
            {
                if(elems[j] > elems[j+1])
                {
                    int temp = elems[j];
                    elems[j] = elems[j+1];
                    elems[j+1] = temp;
                }
            }
        }
    }
    

4.2 选择排序

  • 原理

    基本思想和冒泡排序是一样的,选择排序相对于冒泡排序的优点就是减少交换次数。算法思想都是在序列中找到最大值(最小值),然后存放好下次进入循环就访问不到这个最大值(最小值)。当两层循环都结束的时候序列就自然排好了。

  • 代码实现

    void Swap(int *nums, int idx1, int idx2)
    {
        // 轮换赋值
        int tmp = nums[idx1];
        nums[idx1] = nums[idx2];
        nums[idx2] = tmp;
    }
    int* sortArray(int* nums, int numsSize, int* returnSize){
        *returnSize = numsSize;
        for (int i = 0; i < numsSize - 1; ++i) {    // 选择排序需要经过 n - 1 轮选择
            // 在 nums[i...len-1] 中选出最小的元素
            int midIdx = i; // 最小元素的索引值
            for (int j = i + 1; j < numsSize; ++j) {
                if (nums[j] < nums[midIdx]) {
                    midIdx = j;
                }
            }
            // 交换
            Swap(nums, i, midIdx);
        }
    
        return nums;
    }
    

4.3 插入排序

  • 原理

    基本思想还是冒泡排序,不过插入排序是两边相靠的冒泡,所以在序列部分有序的情况下,插入排序的效率要比冒泡排序效率高。从序列的尾部开始往前比较,如果当前的数据小于(大于)前一个的数据就进行交换,否则进入下一次循环,直到外层循环遍历完整个序列就自然排好序了。

  • 代码实现

    void Swap(int *nums, int idx1, int idx2)
    {
        // 轮换赋值
        int tmp = nums[idx1];
        nums[idx1] = nums[idx2];
        nums[idx2] = tmp;
    }
    int* sortArray(int* nums, int numsSize, int* returnSize){
        *returnSize = numsSize;
        // 把 nums[i] 插入有序数组 nums[0...i-1]
        for (int i = 1; i < numsSize; ++i) {    // 插入排序需要进行 n - 1 轮,
            for (int j = i; j > 0; --j) {
                if (nums[j - 1] > nums[j]) {
                    Swap(nums, j - 1, j);
                } else {    // 提前终止,退出内层循环
                    break;  
                }
            }
            // for (int j = i; j > 0 && nums[j - 1] > nums[j]; --j) {  // 等价写法
            //     Swap(nums, j - 1, j);
            // }
        }
    
        return nums;
    }
    

4.4 快速排序

  • 原理

    快速排序的核心思想是设立一个轴,然后其他数据都和这个轴作比较,最后把轴放在序列的中间,执行完一遍快速排序后左边的数据都比轴小,右边的数据都比轴大。然后递归下去,当递归结束的时候就拍好序了。快速排序的排序很快,但是当数据形成一边倒的情况的时候就发挥不出快速排序的优势。

  • 代码实现

    void Swap(int *nums, int idx1, int idx2)
    {
        int tmp = nums[idx1];
        nums[idx1] = nums[idx2];
        nums[idx2] = tmp;
    }
    
    int Partition(int *nums, int left, int right)
    {
        int pivot = nums[left];
        
        int j = left;
        // 循环不变量 nums[left + 1...j] <= pivot; nums(j, i) > pivot
        for (int i = left + 1; i <= right; ++i) {
            if (nums[i] <= pivot) {
                ++j;
                Swap(nums, j, i);
            }
        }
        Swap(nums, left, j);
    
        return j;
    }
    
    void QuickSort(int *nums, int left, int right)
    {
        if (left >= right) {
            return; 
        }
        int pivotIdx = Partition(nums, left, right);
        QuickSort(nums, left, pivotIdx - 1);
        QuickSort(nums, pivotIdx + 1, right);
    }
    
    int* sortArray(int* nums, int numsSize, int* returnSize){
        QuickSort(nums, 0, numsSize - 1);
        *returnSize = numsSize;
    
        return nums;
    }
    

4.5 归并排序

  • 原理

    把要排序的序列拆分成多个含有一个数据的序列,然后按照从小到大(从大到小)进行合并,这样就自然的将无序的序列排好序。

  • 代码实现

    /* 将两个有序区间nums[left...mid]和nums[mid+1...right]进行合并 */
    void MergeTwoSortedArray(int *nums, int left, int mid, int right, int *tmpNums)
    {
        for (int i = left; i <= right; ++i) {
            tmpNums[i] = nums[i];
        }
        int i = left, j = mid + 1;
        for (int k = left; k <= right; ++k) {
            if (i == mid + 1) {
                nums[k] = tmpNums[j];
                ++j;
            } else if (j == right + 1) {
                nums[k] = tmpNums[i];
                ++i;
            } else if (tmpNums[i] <= tmpNums[j]) {  // 注意是 ≤ 号,写成 < 则会不稳定
                nums[k] = tmpNums[i];
                ++i;
            } else {
                nums[k] = tmpNums[j];
                ++j;
            }
        }
    }
    
    /* 对nums[left...right]进行归并排序 */
    void MergeSort(int *nums, int left, int right, int *tmpNums)
    {
        if (left == right) {
            return;
        }
        int mid = (right + left) >> 1;
        MergeSort(nums, left, mid, tmpNums);
        MergeSort(nums, mid + 1, right, tmpNums);
        // 合并两个有序区间
        MergeTwoSortedArray(nums, left, mid, right, tmpNums);
    }
    
    int* sortArray(int* nums, int numsSize, int* returnSize){
        *returnSize = numsSize;
        int tmpNums[numsSize];  // 辅助数组
        MergeSort(nums, 0, numsSize - 1, tmpNums);
    
        return nums;
    }
    

4.6 希尔排序

  • 原理

    希尔排序是一种基于插入排序的算法,即分组插入排序 或 逐渐缩小间隔的插入排序。

  • 代码实现

    int* sortArray(int* nums, int numsSize, int* returnSize){
        *returnSize = numsSize;
        for (int delta = numsSize / 2; delta > 0; delta /= 2) { // 分组, 逐渐缩小间隔
            for (int start = 0; start < delta; ++start) {   // 对每一组进行处理
                // 分组插入排序
                for (int i = start + delta; i < numsSize; i += delta) {
                    int tmp = nums[i];
                    int j;
                    for (j = i; j - delta >= 0 && nums[j - delta] > tmp; j -= delta) {
                        nums[j] = nums[j - delta];
                    }
                    nums[j] = tmp;
                }
            }
        }
    
        return nums;
    }
    
  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt
16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值