文章目录
数据结构和算法
1 概述
-
定义
数据结构 = 个体的存储 +个体关系的存储 (研究数据的存储)
算法 = 对存储数据的操作 (对数据的操作)
-
衡量算法的标准
-
时间复杂度
大概程序要执行的次数,而非执行的时间
-
空间复杂度
算法执行过程中大概所占用的最大内存
-
难易程度
-
健壮性
-
-
预备知识
-
指针
指针就是地址 地址就是指针
指针变量是存放内存单元地址的变量
指针的本质是一个操作受限的非负整数
一个字节就是一个地址,比方说一个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 线性结构的常见应用——队列
-
定义:
一种可以实现“先进先出”的存储结构
-
分类
-
链式队列——用链表实现
-
静态队列——用数组实现
静态队列通常都必须是循环队列
-
循环队列的讲解:
-
静态队列为什么必须是循环队列
空间利用率
-
循环队列需要几个参数来确定
需要两个参数来确定
-
循环队列各个参数的含义
-
队列初始化:
font和rear的值都是0
-
队列非空:
font代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个元素
-
队列空
font和rear的值相等,但不一定是0
-
-
循环队列入队伪算法讲解
两步完成:
- 将值存入r所代表的位置
- r = (r+1) % 数组的长度
-
循环队列出队伪算法讲解
f = (f+1) % 数组的长度
-
如何判断循环队列是否为空
如果front与rear的值相等,则该队列一定为空
-
如何判断循环队列是否已满
少用一个,如果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+4+…100的和
-
汉诺塔
# 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; }
-
走迷宫
-
-
递归的应用
- 数和森林就是以递归的方式定义的
- 数和图的很多算法都是以递归来实现的
- 很多数学公式就是以递归的方式定义的
3 非线性结构
3.1 树
-
定义
- 专业定义
- 有且只有一个称为根的节点
- 有若干个互不相交的子树,这些子树本身也是一棵树
- 通俗的定义
- 树是由节点和边组成
- 每个节点只有一个父节点但可以有多个子节点
- 但是有一个节点例外,该节点没有父节点,此节点称为根节点
- 专业术语
- 节点 父节点
- 子孙
- 深度:从根节点到最底层节点的层数
- 叶子节点:没有子节点的节点
- 非叶子节点
- 度:子节点的个数称为度
- 专业定义
-
分类
- 一般树
- 任意一个节点的子节点的个数都不受限制
- 二叉树
- 任意一个节点的子节点个数最多两个,且子节点的位置不可更改
- 二叉树的分类
- 二叉搜索树:有序树
- 满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树
- 完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树
- 森林
- 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; }