408-数据结构代码规范
文章目录
- 408-数据结构代码规范
- 序言
- 优化
- 参考文献
- 考前预测
- 线性表-链表
- 单链表
- 静态链表
 
- 栈、队列、数组
- 栈的顺序存储类型
- 队列
 
- 树与二叉树
- 二叉树的定义
- 二叉树的层序遍历
- 树的双亲表示法
- 孩子表示法
- 树的孩子兄弟表示法
 
- 图
- 邻接矩阵存储法
- 邻接表法
 
- 二分查找/折半查找
- 模板
- 区间划分
 
- 快速排序
- 代码
- 分析
 
- 归并排序
- 代码
- 分析
 
- 2009年-链表中倒数第k个节点
- 题目
- 题解
- 代码
 
- 2010年-旋转数组
- 题目
- 题解
- 代码
 
- 2011年-寻找两个正序数组的中位数
- 题目
- 题解
 
- 2017年-二叉树的遍历
- 题目
- 题解
- 代码
 
- 2012年-最佳归并树与哈夫曼树
- 归并排序代码
- 分析
 
- 2012年-字符串匹配(链表的访问)
- 2013年-寻找主元素(栈的处理思想)
- 题解
- 代码
 
- 2014年-计算二叉树的WPL(二叉树遍历)
- 题解
- 代码
 
- 2018年-利用标记数组处理判断正整数是否出现过
- 题目
- 题解
- 代码
 
- 2019年-链表逆置(头插法)
- 题解
- 代码
 
- 2020年-求最小距离(数组的遍历)
- 题目
- 题解
- 代码
 
- 2022年-判断是否是一棵二叉搜索树
- 题目
- 题解
- 代码
 
 
序言
自用。
 本文目的不在于分析题目的解法,而在于规范代码的格式
优化
- 算法思想类型的代码尽可能靠近
参考文献
1 408算法题源 https://leetcode.cn/circle/discuss/3qmfV0/
考前预测
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL) return NULL;
        //头插法
        ListNode * p = head;//工作指针,指向链表的最后一个位置,由于leetcode没有头结点后面相当于把最后一个结点当作头结点使用
        ListNode * q = head;
        while(p->next != NULL){
            p = p->next;
        }
        
        //开始头插
        //工作指针
        p->next = NULL;
        ListNode *r;
        while(q != p){
            r = q->next;
            q->next = p->next;
            p->next = q;
            q = r;
        }
        return p;
    }
};
注意:别让链表成环了
线性表-链表
单链表
typedef struct LNode{
    ElemType data;
    struct Lnode *next;
} LNode, *LinkList;
注:双链表则添加一个指针prior指向前面一个结点
静态链表
#define MaxSize 50
typedef struct{
    ElemType data;
    int next;
} SLinkList[MaxSize];
注:next==-1作为其结束标志
栈、队列、数组
栈的顺序存储类型
#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int top;//栈顶指针
}sqStack;
注:栈顶指针S.top,初始时设置S.top = -1; 栈顶元素 S.data[S.top];
显然,入栈先加1.再赋值,而出栈先取值,再减1
S.top == -1; //栈空条件 
S.top == MaxSize - 1; //栈满条件
int L = S.top + 1; //栈长 
队列
#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front;//队头指针
    int rear;//队尾指针
} queue;
树与二叉树
二叉树的定义
typedef struct BitNode{
    ElemType data;
    struct BitNode *lchild, *rchild;
}BitNode, *BitTree;//一个是结点的声明,一个是指针的声明
二叉树的层序遍历
void LevelOrder(BitTree T){
	InitQueue(Q);
    BitTree p;
    EnQueue(Q, T); //根结点入队
    while(!Q.isempty()){
        DeQueue(Q, p);
        visit(p);
        if (p->lchild != NULL){
            EnQueue(Q, lchild);
        }
        if (p->rchild != NULL){
            EnQueue(Q, rchild);
        }
    }
}
//注:使用stl的代码会更为简洁,内容也没有差距,但还是以王道书(来源考纲)命名为主
树的双亲表示法
如何记忆:用于表示多叉树+双亲
也可以表示森林
#define MaxTreeSize 100
typedef struct{
    ElemType data;
    int parent;
}PTNode;
typedef struct{
    PTNode nodes[MaxTreeSize];
    int n;
}PTree;
孩子表示法
可以用来表示多叉树
//下一个孩子的信息
typedef struct Child{
    int index; //孩子编号
    struct Child *next; //下一个孩子
} Child;
//TreeNode 用于保存结点信息
typedef struct TreeNode{
    char data;
    Child* firstChild;
}TreeNode;
TreeNode tree[10];
树的孩子兄弟表示法
可以借助多叉树化二叉树的思想记忆
typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild, *nextsibling;//第一个孩子和右兄弟指针
}
图
邻接矩阵存储法
#define MaxVertexNum 100 //顶点数目
typedef struct{
    char v[MaxVertexNum]; //顶点表
    EdgeType e[MaxVertexNum][MaxVertexNum]; //边表
    int vnum, arcnum //顶点个数
} MGraph;
邻接表法
略,考察可能性不大
二分查找/折半查找
模板
- start为第一个元素所在位置下标
- end为最后一个元素所在下标
int binary_search(int low, int high, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
  while (low <= high) {
    mid = (low + high) / 2;
    if (arr[mid] < key)
      low = mid + 1;
    else if (arr[mid] > key)
      high = mid - 1;
    else {  // 最后检测相等是因为多数搜索情况不是大于就是小于
      ret = mid;
      break;
    }
  }
  return ret;  // 单一出口
}
优化
int binary_search(int start, int end, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
  while (start <= end) {
    mid = start + ((end - start) >> 1);  // 直接平均可能会溢出,所以用这个算法
    if (arr[mid] < key)
      start = mid + 1;
    else if (arr[mid] > key)
      end = mid - 1;
    else {  // 最后检测相等是因为多数搜索情况不是大于就是小于
      ret = mid;
      break;
    }
  }
  return ret;  // 单一出口
}
区间划分
前言,每种区间划分所导致代码都会有细节上的不同。
假设,区间长度为 [L, R]
- 划分1
将[L, R]区间可以划分为[L, mid], [mid+1, R],那么对应的更新就应该为
R = mid;
L = mid + 1;
快速排序
基于分治的思想,主要由两个步
1)划分
2)排序
代码
void quicksort(int a[], int low, int high){
	if (low < high){
        int pos = partition(a, low, high);
    	quicksort(a, low, pos-1);
        quicksort(a, pos+1, high);
    }
}
//partition是一趟排序
int partition(int a[], int low, int high){
    int pos = a[low];//将表中第一个元素设置位枢轴
    while(low < high){
        //从右边找到第一个比枢轴值小的
        while(low < high && a[high] >= pos) --high;
        a[low] = a[high];
        while(low < high && a[low] >= pos) ++low;
        a[high] = a[low];
    }
    a[low] = pos;
    return low;
}
分析
- 不稳定
- 受初始序列的排序的影响
- 在平衡的划分下,效率高,【选择题考察】
- 快速排序最坏情况下O(n^2)平均情况下O(nlog2n)
归并排序
代码
int *b = (int *) malloc (sizeof(int) * (n+1));
void merge(int a[], int low, int mid, int high){
    //表的两段各自有序将其进行合并
    for(int i = low; i <= high; i++){
        b[i] = a[i];
    }
    int i, j, k;
    for(i = low, j = mid+1, k = 0; i <= mid && j <= high; k++){
        if (b[i] <= b[j]){
            a[k] = b[i++];
        }
        else {
            a[k] = b[j++];
        }
    }
    while(i <= mid){
        a[k++] = b[i++];
    }
    while(j <= high){
        a[k++] = b[j++];
    }
}
void mergesort(int a[], int low, int high){
    if (low < high){
        int mid = (low + high) / 2;
        mergesort(a, low, mid);
        mergesort(a, mid+1, high);
        merge(a, low, mid, high);
    }
}`
分析
-  空间复杂度O(n) 
-  时间复杂度O(nlog2n) 
-  稳定的排序方法 
-  与初始序列的排列无关 
2009年-链表中倒数第k个节点
题目
给你一个链表要你得到倒数第k个数字
题解
双指针移动,第一个指针与第二指针的距离为k,当第一个指针到达最后的位置的时候,第二个指针恰好在倒数第k个位置上。
代码
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode * pre = head;
        ListNode * pos = head;
        int cnt = 0;
        while(pre != NULL){
            if (cnt < k){
                pre = pre->next;cnt++;
            }
            else {
                pre = pre->next;
                pos = pos->next;
            }
        }
        return pos;
    }
};
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        int len = 0;
        ListNode * tmp = head;
        for(; tmp!=NULL; tmp=tmp->next, len++);
        for(int i = 0; i < len - k; i++, head = head->next);
        return head;
    }
};
2010年-旋转数组
题目
将数组nums向右移动k个位置。
题解
- 方法一 空间复杂度o(n),时间复杂度o(n)
每移动k次实际上是没有发生变化的,所以需要取余
k = k % size;
先取出最后的k个数字,然后将前面的数字全部后移,最后将k个数字放在前面。
- 方法二 时间复杂度o(n) 空间复杂度o(1)
进行翻转
先翻转(0,s)
再翻转(0,k)
最后翻转(k, s)
reverse 翻转的实际上翻转的范围是 [first,last)
时间复杂度O(n),空间复杂度O(1)
代码
408答案中从即使是p,n-1的翻转也是从0开始的,可能是有点问题的。
//实现翻转,与下面leetcode题解代码无关
void reverse(int arr[], int n, int a, int b){
	for (int i = a, j = 0; i < (b + a)/2; ++i, j++)
	{
		//swap
		int temp = arr[i];
		arr[i] = arr[b - j];
		arr[b - j] = temp;
	}
}
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        vector<int> temp;
        int s = nums.size();
        k = k % s;
        reverse(nums.begin(), nums.end());
        reverse(nums.begin() + k, nums.end());
        reverse(nums.begin(), nums.begin() + k);
    }
};
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        vector<int> temp;
        int s = nums.size();
        k = k % s;
        for(int i = s - k; i <= s - 1; i++){
            temp.push_back(nums[i]);
        }
        for(int i = 0; i < s - k; i++){
            temp.push_back(nums[i]);
        }
        for(int i = 0; i < s; i++) nums[i] = temp[i];
    }
};
2011年-寻找两个正序数组的中位数
题目
题解
归并
2017年-二叉树的遍历
题目
请设计一个算法,将给定的表达式树(二叉树)转换为等价的中缀表达式(通过括号反映操作符的计算次序)并输出。例如,当下列两棵表达式树作为算法输入时:

输出的中缀表达式分别为 (a+b)∗c(c∗(−d)) 和 (a∗b)+(−(c−d)) 。
二叉树结点的定义如下:
typedef struct node{ 
    char data[10];   //存储操作数或操作符
    struct node *left, *right;     
}BTree;
要求:
⑴ 给出算法的基本设计思想。
⑵ 根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
题解
-  掌握二叉树的遍历,以及运算式的中缀表达式。 
-  二叉树结点的定义(这个也可能成为考题的考察点)。 
-  结果输出的是字符串,注意字符串的输出(细节,可能不会扣分但是也要注意)。 
-  难点在于添加左括号与右括号的位置。 
代码
void Slove(BTree *root){
    InOrder(root, 1);
}
void InOrder(BTree *root, int deep){
	if (root == NULL) return ;
    else if (root->left == NULL && root->right == NULL){
        printf("%s", root->data);
    }
    else {
        //利用deep 这里处理的既不是叶子节点也不是根节点
        if(deep > 1){
            printf("(");
        } //相当于访问左子树,此处左子树添加括号了
        InOrder(root->lef, deep + 1);
        printf("%s", root->data);
        InOrder(root->right, deep + 1);
        if(deep > 1){
            printf(")");
        }
    }
}
2012年-最佳归并树与哈夫曼树
注:本处不提供真题的解答代码,仅提供归并排序的代码,理解归并思想
归并排序代码
int *b = (int *) malloc (sizeof(int) * (n+1));
void merge(int a[], int low, int mid, int high){
    //表的两段各自有序将其进行合并
    for(int i = low; i <= high; i++){
        b[i] = a[i];
    }
    int i, j, k;
    for(i = low, j = mid+1, k = 0; i <= mid && j <= high; k++){
        if (b[i] <= b[j]){
            a[k] = b[i++];
        }
        else {
            a[k] = b[j++];
        }
    }
    while(i <= mid){
        a[k++] = b[i++];
    }
    while(j <= high){
        a[k++] = b[j++];
    }
}
void mergesort(int a[], int low, int high){
    if (low < high){
        int mid = (low + high) / 2;
        mergesort(a, low, mid);
        mergesort(a, mid+1, high);
        merge(a, low, mid, high);
    }
}
分析
-  空间复杂度O(n) 
-  时间复杂度O(nlog2n) 
-  稳定的排序方法 
-  与初始序列的排列无关 
2012年-字符串匹配(链表的访问)
注意链表的定义与从哪个位置开始对比即可
typedef struct LNode{
    ElemType data;
    struct Lnode *next;
} LNode, *LinkList;
2013年-寻找主元素(栈的处理思想)
题解
如果一个序列中的某个元素个数大于n/2,那么完全足够与与其不同的元素逐一相消。
一般来说,是用栈处理,相同入栈,不同出栈,但本题要求高效,所以不用栈处理,但思想上是一致的。
代码
int FindMajority(int a[], int n){
    int res = -1;
    int top = a[0]; //第一个元素入栈
	int cnt = 0; //记入栈顶元素个数
    for(int i = 1; i < n; i++){
        if (a[i] == top){
            cnt++;
        }
        else {
            if (cnt > 0){
                cnt--;
            }
            else {
                //说明当前栈顶无主元素,那么更换主元素
                top = a[i];
                cnt = 1;
            }
        }
    }
    
    int count = 0; //统计栈顶元素实际出现个数
    for(int i = 0; i < n; i++){
		if (top == a[i]){
            count++;
        }
    }
    if (count > n/2){
        return top;
    }
    else {
        return -1;
    }
}
时间复杂度O(n)空间复杂度O(1)
2014年-计算二叉树的WPL(二叉树遍历)
题解
利用全局变量记录二叉树的wpl,先序遍历即可。
代码
typedef struct BiTNode{
	int weight;
	struct BiTNode* lchild, *rchild; 
} BiTNode, *BiTree;
int wpl = 0;//全局变量用于记录二叉树的wpl
void preorder(BiTree * T, int deep){
	if (T->lchild == NULL && T->rchild == NULL){
		wpl += T->weight * deep;
	}
	if (T->lchild != NULL){
		preorder(T->lchild, deep + 1);
	}
	if (T->rchild != NULL){
		preorder(T->rchild, deep + 1);
	}
}
int PrintWpl(BiTree *T){
	//输出结果函数
	preorder(T, 0);
	printf("%d\n", wpl);
}
2018年-利用标记数组处理判断正整数是否出现过
题目
找到最小未出现正整数
题解
标记数组
代码
int FindMissMinNumber(int a[], int n){
	int *t = (int *) malloc(sizeof(int)*(n+2));
    memset(t, 0, sizeof(int) * (n+2));
    for(int i = 0; i < n; i++){
        if (a[i] >= 1 && a[i] <= n){
            t[a[i]] = 1;
        }
    }
    for(int i = 1; i <= n + 1; i++){
        if (t[i] == 0) {
            return 0*printf("%d", i);
        }
    }
}
2019年-链表逆置(头插法)
题解
可以将头插法逆置分解为以下这几个步骤
注:图示2号是要头插的第一个元素,头结点是1号
-  头结点断开 p->next = NULL;
-  当q不为空结点时,说明有元素进行头插法 
 (a)工作指针r指向要进行头插元素的后一个元素r=q->next;
 (b)头结点的下一个结点指向要进行头插的结点,即为q结点p->next=q;[p的值是不会在头插过程中发生改变的,头插二字也可从此理解]
 ©进行头插的结点断开q->next=NULL;
 (d)头插下一个结点q=r;

代码
typedef struct LNode{
    ElemType data;
    struct Lnode *next;
} LNode, *LinkList;
void slove(LinkList * h){
    LinkList *p, *q, *r, *s;//工作指针
    p = q = h;
    //寻找中间结点
    while(q->next != NULL){
        p = p->next;
        q = q->next;
        if (p->next != NULL) p = p->next;
    }
    q = p->next; //令q指向后半段首结点
    p->next = NULL;
    //将后半段链表逆置
    while(q != NULL){
        r = q->next;
        q->next = p->next;
        p->next = q;
        q = r;
    }
    
    //逐一插入即可
    s = h->next;
    q = p->next;
	p->next = NULL;
    while(q != NULL){
        r = q->next;
        q->next = s->next;
        s->next = q;
        s = q->next;
        q = r;
    }
}
2020年-求最小距离(数组的遍历)
题目
计算三元组的最小距离
题解
【答题格式】
算法的基本设计思想
(1)使用mindist记录当前所有已处理过的三元组的最小距离,初值为C语言能表示的最大整数值INT_MAX
(2)将集合S1、S2和S3的序列分别保存在数组A、B、C中。数组大小分别为m,n,p。
(3)分别定义访问数组A、B、C的三个下标变量i,j,k,初始值为0.
(4)当i<m且j<n且j<p时,循环执行(a)-(c)
 (a)计算三元组(A[i],B[j],C[k])的当前距离d
 (b)如果当前距离d<mindist,则更新,mindist=d
 (c)并将A[i],B[j],C[k]当中最小值所在数组的下标+1
(5)输出最小距离mindist
代码
算法实现
#include <limits.h>//最大int整数所在头文件
int abs(int x){
    if (x < 0) return -x;
    else return x;
}
int FindMinDist(int A[], int m, int B[], int n, int C[], int p){
    int mindist = INT_MAX;//mindist = abs(A[0]-B[0])+abs(A[0]-C[0])+abs(B[0]-C[0]);
    int i = 0, j = 0, k = 0;
    while(i < m && j < n && k < p){
        int nowdist = abs(A[i]-B[j])+abs(A[i]-C[k])+abs(B[j]-C[k]);
        if (nowdist < mindist) mindist = nowdist;
        
        if (A[i] < B[j] && A[i] < C[k]) i++;
        else if (B[j] < C[k] && B[j] < A[i]) j++;
        else k++;
    }
    return mindist;
}
时间复杂度O(n+m+p),空间复杂度O(1)
2022年-判断是否是一棵二叉搜索树
题目
非空二叉树使用顺序存储,要求判断是否是二叉搜索树
题解
考察树就要思考树的遍历,而二叉搜索树满足左子树<根<右子树的性质,那么可以考虑中序遍历(左 根 右),中序遍历二叉树可以得到一个升序数列。
算法的基本设计思想
对于采用顺序存储方式保存的二叉树,根结点保存再SqBiTNode[0]中;当某结点保存在SqBiTNode[i]中时,若有左孩子,则其值保存在SqBiTNode[2*i+1]中;
若有右孩子,则其值保存在SqBiTNode[2*i+2]中;若有双亲结点,则其值保存在SqBiTNode[(i-1)/2]中。
使用整型变量val记录中序遍历过程中已遍历结点的最大值,初值可以设置为负数。若当前遍历节点的值小于等于val则说明非二叉搜索树。
否则,将val的值更新为当前结点的值。
代码
val可以设置全局变量或者调用参数。
(如果子树不是二叉搜索树的话,那么以当前结点为根的树也必定不是二叉搜索树)
bool judgeTree(SqBiTree bt, int k, int *val){
    if(k < bt.ElemNum && bt.SqBiTNode[k] != -1){
        if (!judgeTree(bt, 2*k + 1, val)) return false;
        if (bt.SqBiTNode[k] <= *val) return false;
        *val = bt.SqBiTNode[k];
        if (!judgeTree(bt, 2*k + 2, val)) return false;
    }
    return true;
}



















