古典的な面接の質問を積み重ねてキューに入れる

コンテンツ

1.括弧のマッチングの問題

2.キューを使用してスタックを実装します

3.スタックを使用してキューを実装します

4.循環キューを設計する


1.括弧のマッチングの問題

  • 直接リンク:

有効な括弧

  • トピック:

  • アイデア:

問題を解決する前に、まず解決策を明確にする必要があります。スタックのアイデアでこの問題を解決する方が便利です。スタックは、後入れ先出しを明確に示しています。次のように設定できます。

  1. 左括弧"  "、 "  [ "、 " { "に遭遇し、スタックをプッシュします。
  2. 右角かっこ" "、 " ] "、 " } "が見つかったら、スタックポップして左角かっこと一致させます。一致しない場合は、エラーが報告されます。

最後の2つの文の意味は、文字列をトラバースすることです。左角かっこが見つかった場合は、それをスタックにプッシュします。右角かっこが見つかった場合は、スタックの一番上の要素をポップして、次のかどうかを判断します。右角かっこはスタックの一番上の角かっこと一致します。一致しない場合はfalseを返し、トラバーサルが完了するまで一致は文字列をトラバースし続けます。トラバーサル後、スタックが空かどうかを確認します。空で、一致してtrueを返し、それ以外の場合はfalseを返します。

  •  コードは次のように表示されます。
//创建栈结构
typedef char STDataType;
typedef struct Stack
{
	STDataType* a; //存储数据
	int top; //栈顶的位置
	int capacity; //容量
}ST;
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestory(ST* ps);
//压栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//访问栈顶数据
STDataType StackTop(ST* ps);
//有效元素个数
int StackSize(ST* ps);

//定义:
//初始化栈
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}
//销毁栈
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
//压栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	//如果栈满了,考虑扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; //检测容量
		ps->a = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (ps->a == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->capacity = newcapacity; //更新容量
	}
	ps->a[ps->top] = x;//将数据压进去
	ps->top++;//栈顶上移
}
//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}
//判空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0; //如果top为0,那么就为真,即返回
}
//访问栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1]; //top-1的位置才为栈顶的元素
}
//有效元素个数
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//创建好了栈开始实现
bool isValid(char* s) {
    ST st;//先创建一个栈
    StackInit(&st);//初始化栈
    while (*s)
    {
        if (*s == '[' || *s == '(' || *s == '{')
        {
            StackPush(&st, *s); //如果是左括号就入栈
            ++s;
        }
        else
        {
            if (StackEmpty(&st))
            {
                return false; //此处说明前面根本没有左括号,导致栈为空,直接返回false
            }
            char top = StackTop(&st); //获取栈顶元素
            StackPop(&st); //出栈顶元素,接下来进行匹配
            if ((*s == ']' && top != '[')
                || (*s == ')' && top != '(')
                || (*s == '}' && top != '{'))
            {
                StackDestory(&st); //返回前先销毁,防止内存泄漏
                return false; //如果不匹配,直接返回false
            }
            else
            {
                //此时匹配,继续比较,直到遍历结束
                ++s;
            }
        }
    }
    //栈为空,说明所有左括号都匹配
    bool ret = StackEmpty(&st);
    StackDestory(&st); //返回前先销毁,防止内存泄漏
    return ret;
}

2.キューを使用してスタックを実装します

  • 直接リンク:

キューを使用したスタックの実装

  • トピック:

  •  アイデア:

質問をする前に、次の2つの基本的な知識のポイントを明確にしてください

  1. スタック:後入れ先出し
  2. キュー:先入れ先出し

この問題は、FIFOキューを使用してLIFOスタックを実装し、キューを実装するための基本的なインターフェイスをシミュレートします。

数字の文字列があり、キューAに入る順序が1 2 3 4であるとします。このとき、別のキューBがあります。このとき、キューAのヘッドデータを取得してキューBにインポートします。キューAがなくなると、最後だけが残ります。一度にキューAがポップで削除され、この時点でキューBは1 2 3です。間接性は、スタックの機能を実現し、機能を実現することです。ラストイン、ファーストアウトの。データを再入力する必要がある場合は、空でないキューに入力するだけで済みます。そのようにまた出てきてください。短い2つの文で:

  1. プッシュ:空ではないキューにデータをプッシュします。
  2. スタックをポップします。空ではないキューの最初のN-1データを別の空のキューにインポートし、残っている最後のデータを削除します。

本質:データを格納するために一方のキューを保持し、もう一方のキューは空です。スタックをポップするとき、空のキューはデータをガイドするために使用されます。

  •  コードは次のように表示されます。
//创建队列结构
typedef int QDataType; //方便后续更改存储数据类型,本文以int为例
 //创建队列节点
typedef struct QueueNode
{
	QDataType data; //存储数据
	struct QueueNode* next; //记录下一个节点
}QNode;
//保存队头和队尾
typedef struct Queue
{
	QNode* head; //头指针
	QNode* tail; //尾指针
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//入队列
void QueuePush(Queue* pq, QDataType x);
//出队列
void QueuePop(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//获取有效元素个数
size_t QueueSize(Queue* pq);
//获取队头元素
QDataType QueueFront(Queue* pq);
//获取队尾元素
QDataType QueueBack(Queue* pq);

//定义:
//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}
//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}
//入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建一个新节点保存数据
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//暴力检测newnode,因为malloc的都要检测
	assert(newnode);
	newnode->next = NULL;
	newnode->data = x;
	//如果一开始没有数据,为空的情况
	if (pq->tail == NULL)
	{
		assert(pq->head == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}
//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head && pq->tail); //tail和head均不能为空
	//特殊:当删到head=tail的位置时
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//一般情况
	else
	{
		//保存head的下一个节点
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}
//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}
//获取有效元素个数
size_t QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	size_t size = 0;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}
//获取队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head); //头部不能为空
	return pq->head->data;
}
//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail); //尾部不能为空
	return pq->tail->data;
}

/**************创建好队列结构,开始正文模拟实现栈**************/
typedef struct {
	Queue q1; //队列q1
	Queue q2; //队列q2
} MyStack;


MyStack* myStackCreate() {
	MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); //申请一个MyStack类型的栈
	assert(pst);
	QueueInit(&pst->q1);//初始化队列1
	QueueInit(&pst->q2);//初始化队列2
	return pst;
}

void myStackPush(MyStack* obj, int x) {
	assert(obj);
	if (!QueueEmpty(&obj->q1))
	{
		QueuePush(&obj->q1, x);//如果q1不为空,就往q1插入数据
	}
	else
	{
		QueuePush(&obj->q2, x);//这儿不需要知道q2是否为空,直接push
	}
}

int myStackPop(MyStack* obj) {
	assert(obj);
	Queue* emptyQ = &obj->q1; //默认q1为空
	Queue* nonEmtpyQ = &obj->q2;//默认q2不为空
	if (!QueueEmpty(&obj->q1))
	{
		emptyQ = &obj->q2; //若假设错误,则q2为空
		nonEmtpyQ = &obj->q1;//此时q1就为空
	}
	while (QueueSize(nonEmtpyQ) > 1)
	{
		QueuePush(emptyQ, QueueFront(nonEmtpyQ)); //把非空的队列数据导到空的队列,直到只剩一个
		QueuePop(nonEmtpyQ); //此时把非空的队头数据给删掉,方便后续导入数据
	}
	int top = QueueFront(nonEmtpyQ); //记录此时的栈顶数据
	QueuePop(nonEmtpyQ); //删除栈顶数据,使该队列置空
	return top;
}

int myStackTop(MyStack* obj) {
	assert(obj);
	if (!QueueEmpty(&obj->q1))
	{
		return QueueBack(&obj->q1);//如果q1不为空,返回
	}
	else
	{
		return QueueBack(&obj->q2);
	}
}

bool myStackEmpty(MyStack* obj) {
	assert(obj);
	//两个队列均为空,则为空
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
	assert(obj);
	QueueDestory(&obj->q1); //释放q1
	QueueDestory(&obj->q2); //释放q2
	free(obj);
}

3.スタックを使用してキューを実装します

  • 直接リンク:

スタックを使用したキューの実装

  • トピック:

  •  アイデア:

スタックの順序が123 4であると仮定すると、質問の意味に従って、キューを達成するために1 234をポップする順序を達成したいと思います。

この質問は前の質問とは正反対です。スタックはキューを実装するために使用されます。前の質問では、スタックのLIFO機能を実現するために、データを前後にインポートする必要がありますが、この質問は一度だけ前後にインポートする必要はありません。それだけです。

pushSTとpopSTという名前の2つのスタックがあるとします。そして、4つのデータ1 2 3 4をスタックpushSTに入れます。データが出力されるとき、スタックの性質に応じて4つしか取得できません。このとき、4を直接ダウンして、スタックpopSTにインポートします。スタック内のデータをプッシュし続けます。スタックpushSTデータが空になるまでガイドダウンします。現時点では、popSTデータは4 3 2 1であり、これはその逆です。

キューのFIFOルールによると、1 2 3 4では、出力は1 2 3 4である必要があり、スタックpopSTのデータは4 3 2 1であることがわかっています。データが削除されると、次のようになります。 1 234の順に1つずつ削除します。キューの先入れ先出し機能は、スタックの性質を利用するだけで実装されます。そして、一度だけガイドする必要があり、複数回の必要はありません。

  •  コードは次のように表示されます。
//创建栈结构
typedef int STDataType;
typedef struct Stack
{
    STDataType* a; //存储数据
    int top; //栈顶的位置
    int capacity; //容量
}ST;
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestory(ST* ps);
//压栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//访问栈顶数据
STDataType StackTop(ST* ps);
//有效元素个数
int StackSize(ST* ps);

//初始化栈
void StackInit(ST* ps)
{
    assert(ps);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}
//销毁栈
void StackDestory(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->capacity = ps->top = 0;
}
//压栈
void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    //如果栈满了,考虑扩容
    if (ps->top == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity; //检测容量
        ps->a = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
        if (ps->a == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->capacity = newcapacity; //更新容量
    }
    ps->a[ps->top] = x;//将数据压进去
    ps->top++;//栈顶上移
}
//出栈
void StackPop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);
    ps->top--;
}
//判空
bool StackEmpty(ST* ps)
{
    assert(ps);
    return ps->top == 0; //如果top为0,那么就为真,即返回
}
//访问栈顶数据
STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);
    return ps->a[ps->top - 1]; //top-1的位置才为栈顶的元素
}
//有效元素个数
int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}

/************创建好栈结构,开始正文************/
typedef struct {
    ST pushST; //插入数据的栈
    ST popST; //删除数据的栈
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); //申请队列类型
    assert(obj);
    StackInit(&obj->pushST);//初始化pushST
    StackInit(&obj->popST);//初始化popST
    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    assert(obj);
    StackPush(&obj->pushST, x);//不管有没有数据,都插入
}

int myQueuePop(MyQueue* obj) {
    assert(obj);
    if (StackEmpty(&obj->popST)) //如果popST数据为空,要从pushST里导入数据才能删除
    {
        while (!StackEmpty(&obj->pushST)) //pushST数据不为空,就一直向popST里导入数据
        {
            StackPush(&obj->popST, StackTop(&obj->pushST));//把pushST栈顶数据导到popST里
            StackPop(&obj->pushST);//导完后把pushST栈顶元素删掉,方便后续继续导
        }
    }
    int front = StackTop(&obj->popST); //记录popST栈顶元素
    StackPop(&obj->popST);//删除popST栈顶元素,实现队列先进先出
    return front; //返回栈顶数据
}

//取队头数据
int myQueuePeek(MyQueue* obj) {
    assert(obj);
    //如果popST数据为空,要从pushST里导入数据才能取到队头数据
    if (StackEmpty(&obj->popST))
    {
        while (!StackEmpty(&obj->pushST)) //pushST数据不为空,就一直向popST里导入数据
        {
            StackPush(&obj->popST, StackTop(&obj->pushST));//把pushST栈顶数据导到popST里
            StackPop(&obj->pushST);//导完后把pushST栈顶元素删掉,方便后续继续导
        }
    }
    return StackTop(&obj->popST);//直接返回栈顶元素
}

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
}

void myQueueFree(MyQueue* obj) {
    assert(obj);
    StackDestory(&obj->pushST);
    StackDestory(&obj->popST);
    free(obj);
}

4.循環キューを設計する

  • 直接リンク:

循環キューを設計する

  • トピック:

  •  アイデア:

この問題は、配列またはリンクリストを使用して実装できますが、実際には配列を使用する方が便利です。

配列:

キューの先頭と末尾の両方が最初のデータを指していると仮定すると、キューの末尾にデータを挿入し、データの挿入に伴って末尾が移動し、末尾は常に最後のデータの次の位置になります。データを削除するには、キューの先頭を後ろに移動するだけです。循環リンクリストであり、データを再利用できるため、前のキューのポップのようにデータを削除して移動する必要はありません。

この分析と図面には欠陥がないようですが、2つの問題がありますか?

  1. アレイはいつ空になりますか?
  2. アレイはいついっぱいになりますか?

質問1:

tail = frontの場合、配列は空であり、問​​題はないようですが、等しい場合が2つあります。最初に絵を描きます:

 上の図から、最初のケースでは実際に配列にデータがなく、テールもフロントに等しく、配列が空であるという条件を満たすことがわかりますが、2番目のケースでは配列のデータがいっぱいで、テールとフロントも同じです。ここでは、配列は空ではありませんが、いっぱいです。問題があることがわかります。

解決:

ここでは、配列が空か満杯かを区別するために、データを保存せずに余分なスペースを開くことができます。原理は次のとおりです。配列の長さが4の場合、つまり実際には3つの有効なデータを格納でき、別のスペースを使用して空か満杯かを判断します。このときの判断条件空または満杯は次のとおりです。

  1. フロント==テールの場合は空
  2. 尻尾の次の位置が前になると、いっぱいになります

  •  コードは次のように表示されます。
typedef struct {
    int* a; //用数组模拟环形队列
    int front;//队头
    int tail; //队尾
    int k; //表示存的数据长度为k
} MyCircularQueue;

bool myCircularQueueIsFull(MyCircularQueue* obj); //前置声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//前置声明

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));//创建环形链表结构
    assert(obj);
    obj->a = (int*)malloc(sizeof(int) * (k + 1));//多开一个空间,便于后续区分空或满
    obj->front = obj->tail = 0;
    obj->k = k; //队列存储有效数据长度为k
    return obj;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj))
    {
        return false; //队列已满,不能插入数据
    }
    obj->a[obj->tail] = value; //赋值
    if (obj->tail == obj->k)
    {
        obj->tail = 0; //当tail走到尾端
    }
    else
    {
        obj->tail++;
    }
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false; //队列为空,不能删除
    }
    if (obj->front == obj->k)
    {
        obj->front = 0; //当front走到尾端
    }
    else
    {
        obj->front++;
    }
    return true;
}
//取头
int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1; //队列为空,取不了
    }
    return obj->a[obj->front]; //返回队头
}
//取尾
int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1; //队列为空,取不了
    }
    if (obj->tail == 0)
    {
        return obj->a[obj->k]; //tail为0,队尾在长度的最后一个位置
    }
    else
    {
        return obj->a[obj->tail - 1];
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->tail; //front==tail时为空
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if (obj->tail == obj->k && obj->front == 0)
    {
        return true; //当tail尾端,front在头端时也是满
    }
    else
    {
        return obj->tail + 1 == obj->front; //一般情况,当tail的下一个位置为front时为满
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

おすすめ

転載: blog.csdn.net/bit_zyx/article/details/123870918