前言
天干地支纪年法是中国传统文化的重要组成部分,十天干与十二地支依次相配,组成六十甲子 。本文将使用循环队列这一数据结构完成六十甲子的生成,严格遵循题目要求:
- 定义两个循环队列,分别存储十天干、十二地支
- 队列空则重新入队,不为空则各出队一个配对
- 输出完整 60 组甲子
- 提供静态数组版 与动态内存分配版双实现
一、核心知识点
1. 天干地支
- 十天干:甲、乙、丙、丁、戊、己、庚、辛、壬、癸
- 十二地支:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥
- 组合规则:依次配对,循环使用,共 60 种组合

2. 循环队列
- 解决顺序队列假溢出问题
- 判空:
front == rear - 判满:
(rear+1) % maxSize == front(牺牲一个单元)
二、静态数组版 循环队列实现六十甲子
完整代码
cpp
/***********************************************************
* 标题:静态数组版循环队列实现六十甲子
* 环境:C语言 / 无动态内存 / 固定大小数组
**********************************************************/
#include <stdio.h>
#include <string.h>
// 队列最大容量(牺牲1个单元,需 >= 12+1=13 才能存12个地支)
#define MAXSIZE 13 // ? 修复:从12改为13
// 循环队列结构体定义
typedef struct {
// 存储字符串(天干/地支)
char data[MAXSIZE][5];
// 队头指针
int front;
// 队尾指针
int rear;
} Queue;
// -------------------------- 初始化队列 --------------------------
void initQueue(Queue *Q) {
// 队头置0
Q->front = 0;
// 队尾置0
Q->rear = 0;
printf("===== 循环队列初始化完成 =====\n");
}
// -------------------------- 判断队列是否为空 --------------------------
int isEmpty(Queue *Q) {
// 队头 == 队尾 → 空队列
return (Q->front == Q->rear) ? 1 : 0;
}
// -------------------------- 判断队列是否为满 --------------------------
int isFull(Queue *Q) {
// 循环队列判满规则(牺牲1个单元)
return ((Q->rear + 1) % MAXSIZE == Q->front) ? 1 : 0;
}
// -------------------------- 入队操作 --------------------------
int enqueue(Queue *Q, char *elem) {
// 判满
if (isFull(Q)) {
printf("队列已满,无法入队!\n");
return 0;
}
// 字符串拷贝入队
strcpy(Q->data[Q->rear], elem);
// 队尾循环后移
Q->rear = (Q->rear + 1) % MAXSIZE;
return 1;
}
// -------------------------- 出队操作 --------------------------
int dequeue(Queue *Q, char *elem) {
// 判空
if (isEmpty(Q)) {
printf("队列为空,无法出队!\n");
return 0;
}
// 字符串拷贝出队
strcpy(elem, Q->data[Q->front]);
// 队头循环后移
Q->front = (Q->front + 1) % MAXSIZE;
return 1;
}
// -------------------------- 十天干入队 --------------------------
void initTianGan(Queue *Q) {
char *tg[] = {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
for (int i = 0; i < 10; i++) {
enqueue(Q, tg[i]);
}
printf("十天干入队完成\n");
}
// -------------------------- 十二地支入队 --------------------------
void initDiZhi(Queue *Q) {
char *dz[] = {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
for (int i = 0; i < 12; i++) {
enqueue(Q, dz[i]);
}
printf("十二地支入队完成\n");
}
// -------------------------- 主函数 --------------------------
int main() {
// 定义天干、地支两个队列
Queue qTG, qDZ;
// 存储出队元素
char tg[5], dz[5];
// 计数:60个甲子
int count = 0;
// 初始化队列
initQueue(&qTG);
initQueue(&qDZ);
// 首次入队
initTianGan(&qTG);
initDiZhi(&qDZ);
printf("\n========== 六十甲子列表 ==========\n");
// 生成60组
while (count < 60) {
count++;
// 各出队一个
dequeue(&qTG, tg);
dequeue(&qDZ, dz);
// 输出配对
printf("%02d:%s%s\t", count, tg, dz);
// 每6个换行
if (count % 6 == 0) printf("\n");
// 天干空 → 重新入队
if (isEmpty(&qTG)) initTianGan(&qTG);
// 地支空 → 重新入队
if (isEmpty(&qDZ)) initDiZhi(&qDZ);
}
printf("\n========== 生成完成,共60组 ==========\n");
return 0;
}
运行截图如下

三、动态内存分配版 循环队列实现六十甲子
完整代码
cpp
/***********************************************************
* 标题:动态内存版循环队列实现六十甲子
* 说明:本代码故意设置容量=12,用于演示循环队列"牺牲一个单元"
* 因此存入12个地支时会触发:队列已满!(教学演示用)
**********************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 队列容量设置为 12(故意不够用,为了触发"队列已满"报错)
// 因为循环队列会牺牲1个单元,所以只能存 11 个元素
#define DEFAULT_SIZE 12
// 动态循环队列结构体
typedef struct {
char** data; // 动态数组:存储天干/地支字符串
int front; // 队头指针,指向队头元素
int rear; // 队尾指针,指向队尾下一个位置
int maxSize; // 队列最大容量
} Queue;
// -------------------------- 创建队列(动态分配内存) --------------------------
Queue* createQueue(int size) {
// 为队列结构体申请堆空间
Queue* q = (Queue*)malloc(sizeof(Queue));
if (q == NULL) {
printf("结构体分配失败!\n");
return NULL;
}
// 为字符串指针数组申请空间
q->data = (char**)malloc(sizeof(char*) * size);
// 为每个字符串分配空间(存储中文)
for (int i = 0; i < size; i++) {
q->data[i] = (char*)malloc(5);
}
// 初始化队头、队尾都为0
q->front = 0;
q->rear = 0;
q->maxSize = size;
printf("===== 动态循环队列创建成功 =====\n");
return q;
}
// -------------------------- 判断队列是否为空 --------------------------
// 判空条件:队头 == 队尾
int isEmpty(Queue* q) {
return q->front == q->rear;
}
// -------------------------- 判断队列是否为满 --------------------------
// 判满条件:(rear + 1) % maxSize == front
// 特点:会牺牲一个存储单元,用于区分空和满
int isFull(Queue* q) {
return (q->rear + 1) % q->maxSize == q->front;
}
// -------------------------- 入队操作 --------------------------
int enqueue(Queue* q, char* elem) {
// 如果队列满,直接报错返回(本代码故意触发这里)
if (isFull(q)) {
printf("队列已满!\n");
return 0;
}
// 将元素拷贝到队尾
strcpy(q->data[q->rear], elem);
// 队尾循环后移
q->rear = (q->rear + 1) % q->maxSize;
return 1;
}
// -------------------------- 出队操作 --------------------------
int dequeue(Queue* q, char* elem) {
if (isEmpty(q)) {
printf("队列为空!\n");
return 0;
}
// 取出队头元素
strcpy(elem, q->data[q->front]);
// 队头循环后移
q->front = (q->front + 1) % q->maxSize;
return 1;
}
// -------------------------- 十天干入队 --------------------------
void initTG(Queue* q) {
char* tg[] = { "甲","乙","丙","丁","戊","己","庚","辛","壬","癸" };
for (int i = 0; i < 10; i++) {
enqueue(q, tg[i]);
}
}
// -------------------------- 十二地支入队(会触发队列满!) --------------------------
void initDZ(Queue* q) {
char* dz[] = { "子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥" };
for (int i = 0; i < 12; i++) {
enqueue(q, dz[i]);
}
}
// -------------------------- 销毁队列,释放内存 --------------------------
void destroyQueue(Queue* q) {
for (int i = 0; i < q->maxSize; i++) {
free(q->data[i]);
}
free(q->data);
free(q);
printf("队列内存已释放\n");
}
// -------------------------- 主函数 --------------------------
int main() {
// 创建两个队列:天干、地支
Queue* qTG = createQueue(DEFAULT_SIZE);
Queue* qDZ = createQueue(DEFAULT_SIZE);
char tg[5], dz[5];
int count = 0;
// 天干入队(10个,能存下,不报错)
initTG(qTG);
// 地支入队(12个 → 容量12只能存11个 → 触发队列已满!)
initDZ(qDZ);
printf("\n========== 动态版 六十甲子 ==========\n");
while (count < 60) {
count++;
dequeue(qTG, tg);
dequeue(qDZ, dz);
printf("%02d:%s%s\t", count, tg, dz);
if (count % 6 == 0)
printf("\n");
if (isEmpty(qTG))
initTG(qTG);
if (isEmpty(qDZ))
initDZ(qDZ);
}
destroyQueue(qTG);
destroyQueue(qDZ);
return 0;
}
四、核心函数逐行精讲
1. 队列初始化
- 静态版:直接给
front/rear赋值 0 - 动态版:使用
malloc分别为结构体、数组、字符串分配空间
2. 入队函数
- 判断队列是否满
- 字符串拷贝存入队尾
- 队尾指针循环后移
3. 出队函数
- 判断队列是否空
- 队头元素拷贝出来
- 队头指针循环后移
4. 六十甲子生成逻辑
- 两个队列各出队一个元素
- 拼接输出
- 队列空则重新入队
- 循环 60 次结束
你看到 "队满" 还能输出 60 组甲子,是因为:
1. 第一次入队:地支只成功入队 11 个
- 队列容量
DEFAULT_SIZE=12,循环队列判满规则会牺牲 1 个单元 ,所以最多只能存 11 个元素 - 你调用
initDZ()入队 12 个地支:- 前 11 个(子、丑、寅、卯、辰、巳、午、未、申、酉、戌)成功入队
- 第 12 个(亥)入队失败,打印
队列已满!
- 此时队列里有 11 个地支,可以正常出队 11 次,配对输出 11 组甲子
2. 出队后队列变空,再次入队又能存 11 个
- 当这 11 个地支全部出队后,队列变回空队列
- 程序会再次调用
initDZ(),重新尝试入队 12 个地支:- 又成功入队 11 个,第 12 个再次失败 → 再次打印
队列已满! - 又能出队 11 次,配对输出 11 组甲子
- 又成功入队 11 个,第 12 个再次失败 → 再次打印
3. 多次循环后,刚好凑够 60 组
我们算一下:
- 每次入队能得到 11 个可用地支 → 配对 11 组甲子
- 第 1 次:11 组
- 第 2 次:11 组(累计 22)
- 第 3 次:11 组(累计 33)
- 第 4 次:11 组(累计 44)
- 第 5 次:11 组(累计 55)
- 第 6 次:再入队 11 个,取 5 个就凑够 60 组
所以虽然每次都报 "队满",但每次都有 11 个地支可用,多次循环后刚好能输出完整 60 组甲子,不会少。
一句话总结
"队列已满" 只是不让第 12 个地支进队,但已经在队里的 11 个地支可以正常出队、配对;等这 11 个用完,队列变空后又能重新入队 11 个,反复几次就凑够了 60 组。
五、运行效果展示

六、总结
- 循环队列完美解决干支循环配对问题
- 静态版简单易用、适合学习
- 动态版更灵活、支持内存管理