“ 忠诚、笃学、严谨、守纪 ”

点击任意处进入

数据结构笔记2

3 栈和队列

通常称,栈和队列是限定插入和删除只能在表的”端点“进行的线性表

3.1 栈

3.1.1 堆栈的定义:

  • 一个序列,其主要操作是在序列的末尾插入元素和删除元素

特点 :操作首先的线性表;Last Input,First Output(LIFO)

3.1.2 栈的顺序结构定义

  • 封装堆栈元素和栈顶位置

    typedef struct SeqStack{
        int MAXNUM;
        int top;
        DataType *data;
    }SeqStack,*PSeqStack;
    
    PSeqStack pastack;
    
  • 假设堆栈

---------------- (栈底固定,栈顶(top)变化)

3.1.3 堆栈顺序存储时运算的实现

1.创建空栈

PSeqStack creatEmptyStack_seq(int m)
{
    // 1. 给栈结构体本身分配内存
    PSeqStack pastack = (PSeqStack)malloc(sizeof(SeqStack));
    if (pastack == NULL) {
        printf("内存分配失败!\n");
        return NULL;
    }

    // 2. 给栈的元素数组分配内存
    pastack->data = (DataType *)malloc(sizeof(DataType) * m);
    if (pastack->data == NULL) {
        printf("内存分配失败!\n");
        free(pastack); // 分配失败要释放已经申请的结构体内存,避免内存泄漏
        return NULL;
    }

    // 3. 初始化栈顶和最大容量
    pastack->top = -1;       // 栈顶初始化为-1,表示空栈
    pastack->maxSize = m;    // 设置栈的最大容量

    return pastack;
}

2.判断是否为空

int isEmptyStack_seq(PSeqStack pastack)
{
    // 栈顶为-1时表示空栈
    return (pastack->top == -1);
}

3.进栈

int push_seq(PSeqStack pastack, DataType x)
{
    // 1. 检查栈是否已满
    if (pastack->top >= pastack->maxSize - 1)
    {
        printf("栈已满,无法进栈!\n");
        return 0; // 失败返回0
    }

    // 2. 栈顶先+1,再存入数据
    pastack->top++;
    pastack->data[pastack->top] = x;

    return 1; // 成功返回1
}

4.出栈

// 传统写法:出栈,只返回成功/失败,元素直接丢弃
int pop_seq(PSeqStack pastack)
{
    // 1. 检查栈是否为空
    if (pastack->top == -1)
    {
        printf("栈为空,无法出栈!\n");
        return 0; // 失败返回0
    }

    // 2. 栈顶数据出栈,top--
    pastack->top--;

    return 1; // 成功返回1
}
// 带参数写法:出栈,并把元素保存到 *p_x 中
int pop_seq(PSeqStack pastack, DataType *p_x)
{
    // 1. 检查栈是否为空
    if (pastack->top == -1)
    {
        printf("栈为空,无法出栈!\n");
        return 0; // 失败返回0
    }

    // 2. 先取出栈顶元素,保存到 *p_x
    *p_x = pastack->data[pastack->top];

    // 3. 栈顶指针下移
    pastack->top--;

    return 1; // 成功返回1
}

5.取栈顶元素但不删除

// 取栈顶元素(不删除),返回栈顶数据
DataType top_seq(PSeqStack pastack)
{
    // 1. 先判断栈是否为空
    if (pastack->top == -1)
    {
        printf("栈为空,没有栈顶元素!\n");
        // 这里可以根据你的 DataType 返回一个“错误值”,比如 DataType 是 int 时返回 -1
        // 注意:如果你的数据范围包含负数,建议改成带指针参数的版本(下面给你写)
        return -1;
    }

    // 2. 直接返回栈顶元素,不修改 top
    return pastack->data[pastack->top];
}

3.1.4 顺序栈算法的应用(思维)

  • 假设有int数据站S,有一组数字1,2,3,4顺序进栈

Q1:如果数据进栈顺序不允许改变,且每个数据出栈后不允许再进栈,试问有多少种不同的进栈序列存在?

Q2:如果已知一种出栈序列是2,4,3,1,问调用基本操作算法实现进栈、出栈,函数调用的顺序是怎样的?

  • 用一个数组实现两个栈,如何最大程度利用空间?

3.1.5 栈的链式存储

  • 链栈中结点结构:

    typedef struct Node{
        DataType data;
        struct Node *next;
    }*PNode;
    
  • 链栈数据结构:

    typedef struct LinkStack{
        PNode top;
    }*PLinkStack;
    PLinkStack plstack;
    

3.1.6 链栈操作实现

#include <stdio.h>
#include <stdlib.h>

typedef int DataType;

typedef struct Node {
    DataType data;
    struct Node *next;
} *PNode;

typedef struct LinkStack {
    PNode top;
} *PLinkStack;

// 1. 创建空链栈
PLinkStack creatEmptyStack_link()
{
    PLinkStack plstack = (PLinkStack)malloc(sizeof(struct LinkStack));
    if (plstack == NULL)
    {
        printf("内存分配失败!\n");
        return NULL;
    }
    plstack->top = NULL;
    return plstack;
}

// 2. 判断链栈是否为空
int isEmptyStack_link(PLinkStack plstack)
{
    return (plstack->top == NULL);
}

// 3. 入栈
int push_link(PLinkStack plstack, DataType x)
{
    PNode newNode = (PNode)malloc(sizeof(struct Node));
    if (newNode == NULL)
    {
        printf("内存分配失败!\n");
        return 0;
    }
    newNode->data = x;
    newNode->next = plstack->top;
    plstack->top = newNode;
    return 1;
}

// 4. 出栈
int pop_link(PLinkStack plstack, DataType *p_x)
{
    if (isEmptyStack_link(plstack))
    {
        printf("栈为空,无法出栈!\n");
        return 0;
    }
    PNode temp = plstack->top;
    *p_x = temp->data;
    plstack->top = temp->next;
    free(temp);
    return 1;
}

// 5. 取栈顶元素
int top_link(PLinkStack plstack, DataType *p_x)
{
    if (isEmptyStack_link(plstack))
    {
        printf("栈为空,没有栈顶元素!\n");
        return 0;
    }
    *p_x = plstack->top->data;
    return 1;
}

// 6. 销毁链栈
void destroyStack_link(PLinkStack plstack)
{
    PNode temp;
    while (plstack->top != NULL)
    {
        temp = plstack->top;
        plstack->top = temp->next;
        free(temp);
    }
    free(plstack);
}

// 主函数测试
int main()
{
    PLinkStack s = creatEmptyStack_link();
    int x;

    push_link(s, 10);
    push_link(s, 20);
    push_link(s, 30);

    top_link(s, &x);
    printf("栈顶元素:%d\n", x); // 输出 30

    pop_link(s, &x);
    printf("出栈元素:%d\n", x); // 输出 30

    pop_link(s, &x);
    printf("出栈元素:%d\n", x); // 输出 20

    top_link(s, &x);
    printf("栈顶元素:%d\n", x); // 输出 10

    destroyStack_link(s);
    return 0;
}

3.2 栈的应用

3.2.1 栈与递归

递归:若在一个函数、过程或者数据结构定义的内部又直接(或间接)调用现有定义本身的应用,则称它们是递归的,或者是递归定义的

1776822141074.png

递归算法

int fact (int n)
{//设n是非负整数
 if (n==1) //递归终止条件
 return 1;
 else 
return (n*fact(n-1)); //递归步骤
}

从被调用函数返回调用函数之前,应该完成下列三项任务

  • 保存被调函数的计算结果

  • 释放被调函数的数据区

  • 依照被调函数保存的返回地址将控制转移到调用函数

多个函数嵌套调用的规则是后调用先返回

3.2.2 括号配对问题

假设一个算法表达式中包括圆括号、方括号,试设计算法,判别表达式中括号是否正确匹配

(1)32(78-23)+78; (2)(78-23/4)[2+(34+2)/2)]-6;

(3)32/78)-(23+78); (4)[(13-5)/2(6-8+(3*2)])-2+7;

则 检验括号是否匹配的方法可用
“期待的急迫程度”这个概念来描述

考虑下列括号序列
[ ( [ ] [ ] ) ]
1 2 3 4 5 6 7 8

算法设计思想
  1. 凡出现左括号,则进栈

  2. 凡出现右括号,首先检查栈是否空
    若栈空,则表明该“右括号”多余
    否则和栈顶元素比较,

    若相匹配,则“左括号出栈”
    否则表明不匹配

  3. 表达式检验结束时,
    若栈空,则表明表达式中匹配正确
    否则表明“左括号”有余

    算法思想:设表达式以“#”号结束
     while(ch!=“#”){
     读入一个符号ch;
     if (ch==‘[‘||ch==‘(‘) 进栈;
     if (ch==‘]’||ch==‘)’) 
    判断与栈顶元素配对否;
     如果配对,栈顶元素出栈;如果不配对,则出错;
     }
     if (栈空否) 不空,则出错,否则,正确,判断结束
    

3.2.3 表达式计算(

限于二元运算符的表达式定义

表达式 ::= (操作数) + (运算符) + (操作数)
操作数 ::= 简单变量 | 表达式
简单变量 ::= 标识符 | 无符号整数

表达式的三种标识方法

设 Exp = S1 + OP + S2

则称

  • OP + S1 + S2 为前缀表示法

  • S1 + OP + S2 为中缀表示法

  • S1 + S2 + OP 为后缀表示法

1776997248228.png

结论
  1. 操作数之间的相对次序不变

  2. 运算符的相对次序不同

  3. 中缀式丢失了括号信息,致使运算的次序不确定

  4. 前缀式的运算规则:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式

  5. 后缀式的运算规则:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式(栈的经典实现)

1776998528872.png

中缀表达式计算实现

1776998572265.png

1777000068944.png

 设操作数栈OPND,置空;运算符栈OPTR,最低符号#压进OPTR
 读入字符C,C若是操作数, 进OPND;若是运算符,与OPTR栈顶元素(A)比较,根据算符优先级,决定如何处理
A<C, C压入OPTR栈
A=C, A从OPTR出栈
A>C,A出栈,从OPND依次弹出两个操作数y、x, 计算Z=x A y,Z压入OPND栈
 重复,直至表达式结束

//伪代码结构
OperandType EvaluateExpression( ) {
   InitStatck(OPTR); Push(OPTR, ‘#’); //初始化运算符栈
   InitStatck(OPND); c=getchar( );   //初始化数据栈,并读入第一个字符
   while (c!=‘#’||GetTop(OPTR)!=‘#’) {
       if (!In(c, OP)) {Push(OPND, c); c=getchar(); }  //不是运算符则进栈
       else
           switch(Precede(GetTop(OPTR),c)) {
              case ‘<‘:  //栈顶元素优先权低
                   Push(OPTR, c); c=getchar(); 
                   break;
              case ‘=‘:   //脱括号,并接收下一字符
                   Pop(OPTR, x); c=getchar(); 
                   break;
              case ‘>’:  //退栈,并将运算结果入栈
                   Pop(OPTR, theta);
                   Pop(OPND, b); Pop(OPND, a);
                   Push(OPND, Operate(a, theta, b));
                   break;
         }//switch
   }//while
   return GetTop(OPND);
}//EvaluateExpression

//其中(栈PPT page36-37)   深蓝色的算法是关于栈操作的基本算法
                 //浅蓝色的算法是C语言的基本函数,头文件是string.h
                 //红色的算法是需要另外编写的算法

//比较函数
char Precede (int x, int y){ 
char Optr[7][7]={{‘>’,’>’,’<‘,’<‘,’<‘,’>’,’>’},
{‘>’,’>’,’<‘,’<‘,’<‘,’>’,’>’},{‘>’,’>’,’>’,’>’,’<‘,’>’,’>’},
{‘>’,’>’,’>’,’>’,’<‘,’>’,’>’},{‘<‘,’<‘,’<‘,’<‘,’<‘,’=‘,’e’},
{‘>’,’>’,’>’,’>’,’e’,’>’,’>’},{‘<‘,’<‘,’<‘,’<‘,’<‘,’e’,’=‘}
};
return Optr[x][y];
}

//输入的符号转换为数组的下标
int char_Num(char optr)
    {
        switch (optr)
        {          case '+': return 0;
                    case '-': return 1;
                    case '*': return 2;
                    case '/': return 3;
                    case '(': return 4;
               case ')': return 5;
              default: return 6;
        }
}
后缀表达式计算实现

后缀表达式的特点:计算时,无需关心计算符号的优先顺序

后缀表达式计算思想:从左到右读入后缀表达式的各项,并根据读入的对象判断执行的操作。在计算过程中暂时还不能立即参与运算的运算数是入栈暂存

1777001546157.png

每个运算符的运算次序要由它之后的一个运算符来定,在后缀式中,优先级高的运算符领先于(靠左)优先级低的运算符

中缀表达式→后缀表达式:

依次读入中缀表达式,分析

  1. 如果是空格,则认为是分隔符,无须处理

  2. 若遇到运算数,则直接输出

  3. 若是左括号,则将其压入至堆栈中

  4. 若遇到是右括号,表明括号中的中缀表达式已经扫描完毕,将栈顶的运算符弹出,并输出,直到遇到左括号(左括号也出栈,但不输出)

  5. 若遇到的是运算符,若该运算符的优先级大于栈顶运算符的优先级,则把它压栈,若该运算符的优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出,再比较新的栈顶运算符,按同样处理方法,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈

  6. 若中缀表达式中的各对象处理完毕,则把堆栈中存留的运算符一并输出

抄-中缀算法,改-增加中缀->后缀算法,查-各种表达式测试

3.2.4 数值转换

算法基于原理 N = (N div d)×d + N mod d

1777001850433.png

void conversion () {
    InitStack(S); 
    scanf ("%d",N);
    while (N) {
      Push(S, N % 8);
      N = N/8;
    }
    while (!StackEmpty(S)) {
      Pop(S,e);
      printf ( "%d", e );
    }
} // conversion

3.2.5 行编辑程序问题

在用户输入一行的过程中,允许用户输入出差错,并在发现有误时可以及时更正。

合理的作法: 设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区; 并假设“#”为退格符,“@”为退行符。

3.2.6 迷宫求解

穷举求解:

1777421071112.png

设定当前位置的初值为入口位置
 do{
   若当前位置可通,
   则{将当前位置插入栈顶; 
       若该位置是出口位置,则算法结束;            
       否则切换当前位置的东邻方块为
           新的当前位置;
   }
   否则 {
   }
 }while (栈不空);
若栈不空且栈顶位置尚有其他方向未被探索,
则设定新的当前位置为: 沿顺时针方向旋转
找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则{删去栈顶位置;// 从路径中删去该通道块                   
    若栈不空,则重新测试新的栈顶位置,
    直至找到一个可通的相邻块或出栈至栈空;
}
若栈空,则表明迷宫没有通路

3.3 队列

3.3.1 队列的定义

  • 一个序列,但是队列的插入和删除分别是在线性表的两个不同的端点进行

    1777422381934.png

特点:操作受限的线性表;First Input,First Output(FIFO)先进先出

3.3.2 队列的顺序存储结构定义

//封装队列元素和队头队尾位置
typedef struct SeqQueue {
    int MAXNUM;
    int f, r;
    DataType *data;
}*PSeqQueue;
PSeqQueue paqu;
队列的顺序存储
1777422815956.png

“假溢出”问题的处理

  • 将空间想象为一个首尾相接的圆环,即构成环状空1777423214727.png 队列的“空”“满”条件都是paqu->f=paqu->r
循环队列区分“空”与“满”的三种方法
  1. 增加标志位flag

\[flag = \begin{cases} 1, & r = f,\ \text{满} \\ 0, & r = f,\ \text{空} \end{cases} \]

  1. 增加长度变量Size

\[ Size = \begin{cases} = MAXNUM, & \text{满} \\ \neq MAXNUM, & \text{空} \end{cases} \]

  1. 牺牲一个单元空间,区分满和空

    • ( r = f ),空
    • ( (r+1) % MAXNUM = f ),满

3.3.3 队列顺序存储时的运算实现

定义结构

#include <stdio.h>
#include <stdlib.h>

// 定义队列数据类型
typedef int DataType;

// 循环队列结构体(增加 flag 区分空/满)
typedef struct SeqQueue {
    int MAXNUM;     // 队列最大容量
    int f, r;       // f=队头,r=队尾
    int flag;       // 标志位:0=最后出队,1=最后入队
    DataType *data; // 数据数组
} *PSeqQueue;
  1. 创建空队列

    PSeqQueue createEmptyQueue_seq(int m) {
        // 分配队列头空间
        PSeqQueue paqu = (PSeqQueue)malloc(sizeof(struct SeqQueue));
    
        // 分配数组空间
        paqu->data = (DataType*)malloc(m * sizeof(DataType));
    
        paqu->MAXNUM = m;
        paqu->f = paqu->r = 0;  // 队头队尾都指向 0
        paqu->flag = 0;         // 初始为空,标记最后一次是出队
    
        return paqu;
    }
    
  2. 判断是否为空

    int IsEmptyQueue_seq(PSeqQueue paqu) {
        // 空:f==r 且 flag=0
        if (paqu->f == paqu->r && paqu->flag == 0)
            return 1;  // 是空
        else
            return 0;  // 非空
    }
    
  3. 判断是否为满

    int IsFullQueue_seq(PSeqQueue paqu) {
        // 满:f==r 且 flag=1
        if (paqu->f == paqu->r && paqu->flag == 1)
            return 1;  // 满
        else
            return 0;  // 不满
    }
    
  4. 入队

    int enQueue_seq(PSeqQueue paqu, DataType x) {
        // 满了不能入队
        if (IsFullQueue_seq(paqu)) {
            printf("队列已满!\n");
            return 0;
        }
    
        // 放入队尾
        paqu->data[paqu->r] = x;
        // 队尾后移(循环)
        paqu->r = (paqu->r + 1) % paqu->MAXNUM;
    
        paqu->flag = 1;  // 入队 → flag=1
        return 1;
    }
    
  5. 出队

    int deQueue_seq(PSeqQueue paqu) {
        // 空不能出队
        if (IsEmptyQueue_seq(paqu)) {
            printf("队列为空!\n");
            return 0;
        }
    
        // 队头后移(循环)
        paqu->f = (paqu->f + 1) % paqu->MAXNUM;
    
        paqu->flag = 0;  // 出队 → flag=0
        return 1;
    }
    
  6. 取队头元素

    DataType frontQueue_seq(PSeqQueue paqu) {
        return paqu->data[paqu->f];
    }
    

3.3.4 队列的链式存储

#include <stdio.h>
#include <stdlib.h>

// 数据类型定义
typedef int DataType;

// 1. 链式队列节点结构
typedef struct QNode {
    DataType data;          // 数据域
    struct QNode *next;     // 指针域
} QNode, *QNodePtr;

// 2. 队列头结构(队头、队尾指针)
typedef struct {
    QNodePtr f;  // 队头指针 front
    QNodePtr r;  // 队尾指针 rear
} LinkQueue, *PLinkQueue;

// ==========================================
// 3. 初始化空队列(带头结点)
// ==========================================
PLinkQueue InitQueue() {
    PLinkQueue plqu = (PLinkQueue)malloc(sizeof(LinkQueue));
    QNodePtr head = (QNodePtr)malloc(sizeof(QNode));
    head->next = NULL;

    plqu->f = head;
    plqu->r = head;
    return plqu;
}

// ==========================================
// 4. 判断队列是否为空
// ==========================================
int IsEmptyQueue(PLinkQueue plqu) {
    return plqu->f == plqu->r;
}

// ==========================================
// 5. 入队操作(尾部插入)
// ==========================================
int EnQueue(PLinkQueue plqu, DataType x) {
    // 创建新节点
    QNodePtr p = (QNodePtr)malloc(sizeof(QNode));
    p->data = x;
    p->next = NULL;

    // 插入队尾
    plqu->r->next = p;
    plqu->r = p;
    return 1;
}

// ==========================================
// 6. 出队操作(头部删除)
// ==========================================
int DeQueue(PLinkQueue plqu, DataType *x) {
    if(IsEmptyQueue(plqu)) {
        printf("队空,不能出队\n");
        return 0;
    }

    QNodePtr p = plqu->f->next;
    *x = p->data;  // 取出元素

    plqu->f->next = p->next;

    // 如果是最后一个节点,队尾指针回退
    if(p == plqu->r) {
        plqu->r = plqu->f;
    }

    free(p);
    return 1;
}

// ==========================================
// 7. 取队头元素(不删除)
// ==========================================
DataType GetFront(PLinkQueue plqu) {
    return plqu->f->next->data;
}

// ==========================================
// 8. 销毁队列
// ==========================================
void DestroyQueue(PLinkQueue plqu) {
    QNodePtr p, q;
    p = plqu->f;
    while(p) {
        q = p->next;
        free(p);
        p = q;
    }
    free(plqu);
}

// ==========================================
// 主函数测试
// ==========================================
int main() {
    PLinkQueue q = InitQueue();
    int x;

    // 入队
    EnQueue(q, 10);
    EnQueue(q, 20);
    EnQueue(q, 30);

    printf("队头元素:%d\n", GetFront(q));

    // 出队
    DeQueue(q, &x);
    printf("出队:%d\n", x);
    printf("现在队头:%d\n", GetFront(q));

    DeQueue(q, &x);
    printf("出队:%d\n", x);

    DeQueue(q, &x);

3.3.5 队列算法的应用

已知一个字符串,判断该字符串是否为回文

地满红花红满地,天连碧水碧连天, 洞帘水挂水帘洞

用数组存储字符串,长度/2,从两头开始取字符,并进行判断

int Palindrome(char *str){
    int i;
    char ch1, ch2;
    int len = length(str);  // 求字符串长度

    // 第一步:所有字符 同时入栈 + 入队
    for (i = 0; i < len; i++) {
        push(s, str[i]);    // 入栈
        enqueue(q, str[i]); // 入队
    }

    // 第二步:逐个出栈 + 出队 比较
    while (!IsEmpty(s)) {
        ch1 = top(s);  pop(s);   // 取栈顶(最后面的字符)
        ch2 = getQ(q); deQueue(q); // 取队头(最前面的字符)

        if (ch1 == ch2) {
            // 字符相等,继续比较下一个
            continue;
        } else {
            // 不相等 → 不是回文,直接返回 0
            return 0;
        }
    }

    // 全部比较完都相等 → 是回文
    return 1;
}
    return 1;
}
计算n行杨辉三角的值
#include <stdio.h>
#include <stdlib.h>

#define MAXSIZE 100

// 循环队列结构体(带标志位区分空/满)
typedef struct {
    int data[MAXSIZE];
    int front, rear;
    int flag; // 0: 空,1: 满
} SeqQueue;

// 初始化队列
void InitQueue(SeqQueue *Q) {
    Q->front = Q->rear = 0;
    Q->flag = 0;
}

// 入队
int EnQueue(SeqQueue *Q, int x) {
    if (Q->front == Q->rear && Q->flag == 1) {
        printf("队列已满\n");
        return 0;
    }
    Q->data[Q->rear] = x;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    Q->flag = 1;
    return 1;
}

// 出队
int DeQueue(SeqQueue *Q, int *x) {
    if (Q->front == Q->rear && Q->flag == 0) {
        printf("队列为空\n");
        return 0;
    }
    *x = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
    Q->flag = 0;
    return 1;
}

// 取队头元素(不删除)
int GetHead(SeqQueue *Q, int *e) {
    if (Q->front == Q->rear && Q->flag == 0) {
        return 0;
    }
    *e = Q->data[Q->front];
    return 1;
}

// 生成并打印n行杨辉三角
void YangHuiTriangle(int n) {
    SeqQueue Q;
    InitQueue(&Q);
    int s, e;

    // 初始:第1行 [1, 0]
    EnQueue(&Q, 1);
    EnQueue(&Q, 0);

    for (int i = 1; i <= n; i++) {
        printf("第%d行:", i);

        do {
            DeQueue(&Q, &s);
            GetHead(&Q, &e);
            if (e != 0) {
                printf("%d ", e);
            }
            EnQueue(&Q, s + e);
        } while (e != 0);

        printf("\n");
    }
}

int main() {
    int n;
    printf("请输入要生成的杨辉三角行数:");
    scanf("%d", &n);

    YangHuiTriangle(n);
    return 0;
}
划分无冲突子集问题

某运动会设立 N 个比赛项目,每个运动员可以参加一至三个项目。试问如何安排比赛日程既可以使同一运动员参加的项目不安排在同一单位时间进行,又使总的竞赛日程最短。

若将此问题抽象成数学模型,则归属于“划分子集”问题。N 个比赛项目构成一个大小为 n 的集合,有同一运动员参加的项目则抽象为“冲突”关系。

屏幕截图 2026-04-29 092330.png

1777426630989.png

#include <stdio.h>
#include <stdlib.h>
#define N 9      // 9个比赛项目 0~8
#define MAXN 100 // 队列最大容量

// ===================== 顺序(循环)队列 =====================
typedef struct {
    int data[MAXN];
    int front, rear;
} SeqQueue;

// 初始化队列
void InitQueue(SeqQueue *Q) {
    Q->front = Q->rear = 0;
}

// 判断队列空
int IsEmpty(SeqQueue *Q) {
    return Q->front == Q->rear;
}

// 入队
int EnQueue(SeqQueue *Q, int x) {
    if ((Q->rear + 1) % MAXN == Q->front) return 0;
    Q->data[Q->rear] = x;
    Q->rear = (Q->rear + 1) % MAXN;
    return 1;
}

// 出队
int DeQueue(SeqQueue *Q, int *x) {
    if (IsEmpty(Q)) return 0;
    *x = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXN;
    return 1;
}

// ===================== 冲突矩阵(题目给的) =====================
int conflict[N][N] = {
    {0,1,0,0,0,1,1,0,0},
    {1,0,0,0,1,1,0,1,1},
    {0,0,0,0,0,1,1,0,0},
    {0,0,0,0,1,0,0,0,1},
    {0,1,0,1,0,0,1,0,1},
    {1,1,1,0,0,0,1,0,0},
    {1,0,1,0,1,1,0,0,0},
    {0,1,0,0,0,0,0,0,0},
    {0,1,0,1,1,0,0,0,0}
};

// ===================== 核心算法:划分子集(日程安排) =====================
void Schedule() {
    SeqQueue Q;
    InitQueue(&Q);

    int group[N];   // 每个项目属于第几组(时间)
    int ***[N];   // 本组冲突项目标记
    int pre = N;    // 前一个出队元素
    int groupNo = 0;// 当前组号
    int i, x;

    // 1. 全体元素入队
    for (i = 0; i < N; i++) {
        EnQueue(&Q, i);
    }

    // 2. 过筛法划分子集
    while (!IsEmpty(&Q)) {
        DeQueue(&Q, &x);  // 队头出队

        // ========== 开辟新组 ==========
        if (x < pre) {
            groupNo++;
            // 清空 ***
            for (i = 0; i < N; i++) ***[i] = 0;
        }

        // ========== 判断能否入组 ==========
        if (***[x] == 0) {
            // 能入组
            group[x] = groupNo;
            // 标记所有和 x 冲突的项目,本组不能再进
            for (i = 0; i < N; i++) {
                if (conflict[x][i]) ***[i] = 1;
            }
        } else {
            // 不能入组 → 重新入队
            EnQueue(&Q, x);
        }

        pre = x;
    }

    // ===================== 输出结果 =====================
    printf("项目编号:0 1 2 3 4 5 6 7 8\n");
    printf("分组编号:");
    for (i = 0; i < N; i++) {
        printf("%d ", group[i]);
    }
    printf("\n\n最少需要 %d 个时间段\n", groupNo);
}

// ===================== 主函数 =====================
int main() {
    Schedule();
    return 0;
}= g) {
                printf("%d ", i);
            }
        }
        printf("\n");
    }

    return 0;
}
posted @ 2026-04-22 09:46  alonep  阅读(7)  评论(0)    收藏  举报