C和C++程序员面试秘笈之⑧

本系列博客基于董山海的<C和C++程序员面试秘笈>,旨在记录,欢迎交流,可联系 zywang@shu.edu.cn !


第八章:数据结构


1、面试题1

编程实现一个单链表的建立

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//创建单链表
node *create() {
	int i = 0;	//链表中数据的个数
	node *head, *p, *q;
	int x = 0;
	head = (node*)malloc(sizeof(node));		//创建头节点
	//
	while (1) {
		printf("Please input the data: ");
		scanf("%d", &x);
		if (x == 0)		//data为0时创建结束
			break;
		p = (node *)malloc(sizeof(node));
		p->data = x;
		//
		if (++i == 1) {		//链表只有一个元素
			head->next = p;		//连接到 head 后面
		}
		else {
			q->next = p;	//连接到链表尾端
		}
		q = p;	//q指向末节点
	}
	q->next = NULL;		//链表的最后一个指针为 NULL
	return head;
}

使用 while 循环每次从终端读入一个整型数据,并调用 malloc 动态分配链表节点内存存储这个整型数据,然后插到单链表的末尾。最后,当数据为 0 时表示插入数据结束。此时把末尾节点的 next 指针置为 NULL。


2、面试题2

编程实现一个单链表的测长

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

int length(node *head) {
	int len = 0;
	node *p;
	p = head->next;
	while (p != NULL) {
		len++;
		p = p->next;
	}
	return len;
}

3、面试题3

编程实现一个单链表的打印

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

void print(node *head) {
	node *p;
	int index = 0;
	//如果链表为空
	if (head->next == NULL) {
		printf("Link is empty!\n");
		return;
	}
	//
	p = head->next;
	while (p != NULL) {
		printf("The %dth node is: %d\n", ++index, p->data);
		p = p->next;
	}
}

4、面试题4

编程实现一个单链表节点的查找

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//查找单链表 pos 位置的节点,返回节点指针
//pos 从 0 开始,0 返回 head 节点
node *search_node(node *head, int pos) {
	node *p = head->next;
	//
	if (pos < 0) {	//pos 位置不正确
		printf("incorrect position to search node!\n");
		return NULL;
	}
	//
	if (pos == 0) {	//在head位置,返回head
		return head;
	}
	//
	if (p == NULL) {
		printf("Link is empty!\n");	//链表为空
		return NULL;
	}
	//
	while (--pos) {
		if ((p = p->next) == NULL) {
			printf("incorrect position to search node!\n");
			break;
		}
	}
	return p;
}

5、面试题5

编程实现一个单链表节点的插入

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

node *search_node(node *head, int pos) {
	node *p = head->next;
	//
	if (pos < 0) {	//pos 位置不正确
		printf("incorrect position to search node!\n");
		return NULL;
	}
	//
	if (pos == 0) {	//在head位置,返回head
		return head;
	}
	//
	if (p == NULL) {
		printf("Link is empty!\n");	//链表为空
		return NULL;
	}
	//
	while (--pos) {
		if ((p = p->next) == NULL) {
			printf("incorrect position to search node!\n");
			break;
		}
	}
	return p;
}

//在单链表 pos 位置处插入节点,返回链表头指针
//在 pos 从 0 开始计算,0 表示插入到 head 节点后面
node *insert_node(node *head, int pos, int data) {
	node *item = NULL;
	node *p;
	//
	item = (node *)malloc(sizeof(node));
	item->data = data;
	//插入链表头后面
	if (pos == 0) {
		head->next = item;	//head后面为item,指针为NULL,data为data
		return head;
	}
	//
	p = search_node(head, pos);
	//
	if (p != NULL) {
		item->next = p->next;
		p->next = item;		//item表示指针地址
	}
	return head;
}

向单链表中某个位置(第 pos 个节点)之后插入节点,分为插入链表首部、插入到链表中间、以及链表尾端3种情况


6、面试题6

编程实现一个单链表节点的删除

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

node *search_node(node *head, int pos) {
	node *p = head->next;
	//
	if (pos < 0) {	//pos 位置不正确
		printf("incorrect position to search node!\n");
		return NULL;
	}
	//
	if (pos == 0) {	//在head位置,返回head
		return head;
	}
	//
	if (p == NULL) {
		printf("Link is empty!\n");	//链表为空
		return NULL;
	}
	//
	while (--pos) {
		if ((p = p->next) == NULL) {
			printf("incorrect position to search node!\n");
			break;
		}
	}
	return p;
}

//删除单链表的 pos 位置的节点,返回链表头指针
//pos 从1开始计算,1 表示删除 head 后的第一个节点
node *delete_node(node *head, int pos) {
	node *item = NULL;
	node *p = head->next;
	//链表为空
	if (p == NULL) {
		printf("Link is empty()!\n");
		return NULL;
	}
	//
	p = search_node(head, pos - 1);
	//这边注意第一句话
	if (p != NULL&&p->next != NULL) {
		item = p->next;
		p->next = item->next;
		delete item;
	}
	return head;
}

7、面试题7

实现一个单链表的逆置

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//
node *reverse(node *head) {
	node *p, *q, *r;
	//链表为空
	if (head->next == NULL) {
		return head;
	}
	//
	p = head->next;
	q = p->next;	//保存原第2个节点
	p->next = NULL;		//保存原第1个节点为末节点
	//遍历,各个节点的next指针反转
	while (q != NULL) {
		r = q->next;
		q->next = p;
		p = q;
		q = r;
	}
	//新的第1个节点为原末节点
	head->next = p;
	return head;
}

遍历一遍链表,利用一个辅助指针,在存储的过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后继续遍历


8、面试题8

寻找单链表的中间元素

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//
node *search(node *head) {
	int i = 0, j = 0;
	node *current = NULL;
	node *middle = NULL;
	//
	current = middle = head->next;
	while (current !=NULL){
		if (i / 2 > j) {
			j++;
			middle = middle->next;
		}
		i++;
		current = current->next;
	}
	return middle;
}

假设 mid 指向当前已经扫描的子链表的中间元素,cur 指向当前已扫描链表的末节点,那么继续扫描移动 cur 到 cur->next,这时只需要判断一下应不应该移动 mid 到 mid ->next 就行了


9、面试题9

单链表的正向排序

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//
node* InserSort(void) {
	int data = 0;
	struct node *head = NULL;
	struct node *New, *Cur, *Pre;
	//
	while (1) {
		printf("please input the data\n");
		scanf("%d", &data);
		//输入0结束
		if (data == 0) {
			break;
		}
		//新分配一个node节点
		New = (struct node*)malloc(sizeof(struct node));	//分配一个node内存
		New->data = data;
		New->next = NULL;
		//第一次循环时对头节点赋值
		if (head == NULL) {
			head = New;
			continue;
		}
		//head之前插入节点
		if (New->data <= head->data) {
			New->next = head;
			head = New;
			continue;
		}
		//找到需要插入的位置
		Cur = head;
		while (New->data > Cur->data&&Cur->next != NULL) {
			Pre = Cur;
			Cur = Cur->next;
		}
		//位置在中间
		//把New节点插入到Pre和Cur之间
		if (Cur->data >= New->data) {
			Pre->next = New;
			New->next = Cur;
		}
		else { //位置在末尾
			Cur->next = New;
		}
	}
	return head;
}

10、面试题10

判断链表是否存在环形链表的问题

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//判断是否存在回环
//如果存在,start存放回环开始的节点
bool IsLoop(node* head, node **start) {
	node* p1 = head, *p2 = head;
	//head为NULL或者链表为空时,返回false
	if (head == NULL || head->next == NULL) {
		return false;
	}
	//
	do {
		p1 = p1->next;		//p1走一步
		p2 = p2->next->next;	//p2走两步
	} while (p2&&p2->next&&p1 != p2);
	//
	if (p1 == p2) {
		*start = p1;	//p1为回环开始节点
		return true;
	}
	else {
		return false;
	}
}

设置两个指针 p1、p2。每次循环 p1 向前走一步, p2 向前走两步。直到 p2 碰到 NULL 指针或者两个指针相等时结束循环。如果两个指针相等,则说明存在环。


11、面试题11

有序单链表的合并
已知两个链表 head1 和 head2 各自有序,请把它们合并成一个链表,依然有序。使用非递归方法以及递归方法

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

int length(node *head) {
	int len = 0;
	node *p;
	p = head->next;
	while (p != NULL) {
		len++;
		p = p->next;
	}
	return len;
}

//非递归的方法
//只需要把较短链表的各个元素有序地插入到较长的链表之中
node* insert_node(node *head, node *item) {		//head != NULL
	node *p = head;
	node *q = NULL;
	//始终指向P之前的节点
	while (p->data < item->data && p != NULL) {
		q = p;
		p = p->next;
	}
	//插入到原头节点之前。这边注意
	if (p == head) {
		item->next = p;
		return item;
	}
	//插入到q与p之间
	q->next = item;
	item->next = p;
	return head;
}

/*两个有序链表进行合并*/
node* merge(node* head1, node* head2) {
	node* head;		//合并后的头指针
	node *p;
	node *nextP;	//指向p之后
	//有一个链表为空的情况,直接返回另一个链表
	if (head1 == NULL) {
		return head2;
	}
	else if (head2 == NULL) {
		return head1;
	}
	//两个链表都不为空
	if (length(head1) >= length(head2)) {	//选取较短的链表,这样进行的插入次数要少些
		head = head1;
		p = head2;
	}
	else {
		head = head2;
		p = head1;
	}
	//
	while (p != NULL) {
		nextP = p->next;
		head = insert_node(head, p);
		p = nextP;
	}
	return head;
}

这里 insert_node() 函数是有序的插入节点,传入的参数是 node指针类型。然后在 merge() 函数中循环把短链表中所有节点插入到长链表之中

链表1:1->3->5
链表2:2->4->6

下面介绍递归方法

  1. 比较链表1和链表2的第一个节点数据。由于1<2,因此把结果链表头节点指向链表1中第一个节点,即数据1所在的节点
  2. 对剩余的链表1(3->5)和链表2再调用本过程,比较得到结果链表的第二个节点,即2与3比较得到2。此时合并后的链表节点为1->2

如此递归,直到两个链表的表头都被加到结果链表中

#include <iostream>
#include <stdio.h>
using namespace std;

//
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

node *MergeRecursive(node *head1, node *head2) {
	node *head = NULL;
	//
	if (head1 == NULL) {
		return head2;  
	}
	//
	if (head2 == NULL) {
		return head1;
	}
	//
	if (head1->data < head2->data) {
		head = head1;
		head->next = MergeRecursive(head1->next,head2);
	}
	else {
		head = head2;
		head->next = MergeRecursive(head1, head2->next);
	}
	//
	return head;
} 

12、面试题12

循环链表的操作:

编号为1,2…,N的N个人按顺时针方向围坐一圈,每个人持有一个密码(正整数),一开始任选一个正整数作为报数上限值 M,从第一个人开始按顺时针方向自1开始按顺序报数,报到M时停止报数。报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止,求出列顺序

思路
显然当有人退出圆圈后,报数的工作要从下一个人开始继续,而剩下的人仍然是围成一个圆圈的,因此可以使用循环单链表。由于退出圆圈的工作对应着表中节点的删除工作,对于这种删除操作频繁的情况,选用效率较高的链表结构。为了程序指针每一次都指向一个具体的代表一个人的结点而不需要判断。链表不带头节点。所以,对于所有人围成的圆圈所对应的数据结构采用一个不带头节点的循环链表描述。设头指针为p,并根据具体情况移动

为了记录退出的人的先后顺序,采用一个顺序表进行存储。程序结束后再输出依次退出的人的编号顺序。由于只记录各个节点的data值就可以了,所以定一个整型一维数组。如 int quit[n];n 为一个根据实际问题定义一个足够大的整数

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct node {
	int data;	//节点内容
	node *next;	 //下一个节点
}node;

//构造节点数量为 n 的单向循环链表
node * node_create(int n) {
	node *pRet = NULL;
	//
	if (0 != n) {
		int n_idx = 1;
		node *p_node = NULL;
		//构造n个node*
		p_node = new node[n];
		if (NULL == p_node) {
			return NULL;
		}
		else {
			memset(p_node, 0, n * sizeof(node));	//初始化内存
		}
		//
		pRet = p_node;
		while (n_idx < n) {
			p_node->data = n_idx;
			p_node->next = p_node + 1;
			p_node = p_node->next;
			n_idx++;
		}
		p_node->data = n;
		p_node->next = pRet;
	}
	return pRet;
}
//
int main() {
	node *pList = NULL;
	node *pIter = NULL;
	int n = 20;
	int m = 6;
	//构造单项循环链表
	pList = node_create(n);
	//循环取数
	pIter = pList;
	m = m%n;
	while (pIter != pIter->next) {
		int i = 1;
		//取到第m-1个节点
		for (; i < m - 1; i++) {
			pIter = pIter->next;
		}
		//输出第m个节点的值
		printf("%d", pIter->next->data);
		//从链表中删除第m个节点
		pIter->next = pIter->next->next;
		pIter = pIter->next;
	}
	printf("%d\n", pIter->data);
	//释放
	delete[]pList;

	system("pause");
	return 0;
}

13、面试题13

编程实现一个双向链表

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;

//根据 数据 创建节点,返回新创建的节点,给出数据
DbNode *CreateNode(int data) {
	DbNode *pnode = (DbNode *)malloc(sizeof(DbNode));
	pnode->data = data;
	pnode->left = pnode;
	pnode->right = pnode;
	return pnode;
}

//创建链表,根据一个节点创建链表的表头,返回表头节点
DbNode *CreateList(int head) {
	DbNode *pnode = (DbNode *)malloc(sizeof(DbNode));
	pnode->data = head;
	pnode->left = pnode;
	pnode->right = pnode;  //让其前驱和后继指针都指向自身
	return pnode;
}

//插入新节点,总是在表尾插入,返回表头节点
DbNode *AppendNode(DbNode *head, int data) {	//参数1为链表的头节点
	DbNode *node = CreateNode(data);			//参数2是要插入的节点,其数据为data
	DbNode *p = head, *q;
	//
	while (p != NULL) {
		q = p;
		p = p->right;
	}
	q->right = node;
	node->left = q;
	//
	return head;
}

14、面试题14

编程实现一个双向链表的测长
使用 right 指针进行遍历,直到遇到 NULL 为止

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;

//获得链表的长度
int GetLength(DbNode *head) {	//参数为链表的表头节点
	int count = 1;
	DbNode *pnode = NULL;
	// head 为 NULL 表示链表空
	if (head == NULL) {
		return 0;
	}
	pnode = head->right;
	while (pnode != NULL) {
		pnode = pnode->right;	//使用right指针遍历
		count++;
	}
	return count;
}


15、面试题15

编程实现一个双向链表的打印

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;

//打印整个链表
void PrintList(DbNode *head) {
	DbNode *pnode = NULL;
	//
	if (head == NULL) {
		return;
	}
	pnode = head;
	while (pnode != NULL) {
		printf("%d", pnode->data);
		pnode = pnode->right;
	}
	printf("\n");
}

16、面试题16

编程实现双向链表节点的查找

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;

//查找节点,成功则返回满足条件的节点指针,否则返回 NULL
DbNode *FindNode(DbNode *head, int data) {	//参数1:链表的表头节点 ,参数2:要查找的节点。其数据为data
	DbNode *pnode = head;
	//链表为空时返回NULL
	if (head == NULL) {
		return NULL;
	}
	//找到数据或者到达链表末尾,退出while循环
	while (pnode->right != NULL && pnode->data != data) {
		pnode = pnode->right;
	}
	//没有找到数据为data的节点,返回NULL
	if (pnode->right == NULL) {
		return NULL;
	}
	//
	return pnode;
}


17、面试题17

编程实现双向链表的插入
节点 p 后插入一个节点
分为两种情况

  1. 插入位置在中间
  2. 插入位置在末尾

不同:插入位置在中间时需要把 p 的原后继节点的前驱指针指向新插入的节点

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;


//根据 数据 创建节点,返回新创建的节点,给出数据
DbNode *CreateNode(int data) {
	DbNode *pnode = (DbNode *)malloc(sizeof(DbNode));
	pnode->data = data;
	pnode->left = pnode;
	pnode->right = pnode;
	return pnode;
}

//在node节点之后插入新节点
void InsertNode(DbNode *node, int data) {
	DbNode *newnode = CreateNode(data);
	DbNode *p = NULL;
	//node为空的时候直接返回
	if (node == NULL) {
		return ;
	}
	//node为最后一个节点,接在最后一个节点后面
	if (node->right == NULL) {
		node->right = newnode;
		newnode->left = node;
	}
	else {	//node为中间节点
		newnode->right = node->right;	//newnode向右连接
		node->right->left = newnode;
		node->right = newnode;	  //newnode向左连接
		newnode->left = node;
	}
}

18、面试题18

编程实现一个双向链表节点的删除
删除头节点、删除中间节点以及删除末节点

#include <iostream>
#include <stdio.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点内容
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;


//根据 数据 创建节点,返回新创建的节点,给出数据
DbNode *CreateNode(int data) {
	DbNode *pnode = (DbNode *)malloc(sizeof(DbNode));
	pnode->data = data;
	pnode->left = pnode;
	pnode->right = pnode;
	return pnode;
}
//
//查找节点,成功则返回满足条件的节点指针,否则返回 NULL
DbNode *FindNode(DbNode *head, int data) {	//参数1:链表的表头节点 ,参数2:要查找的节点。其数据为data
	DbNode *pnode = head;
	//链表为空时返回NULL
	if (head == NULL) {
		return NULL;
	}
	//找到数据或者到达链表末尾,退出while循环
	while (pnode->right != NULL && pnode->data != data) {
		pnode = pnode->right;
	}
	//没有找到数据为data的节点,返回NULL
	if (pnode->right == NULL) {
		return NULL;
	}
	//
	return pnode;
}
//删除满足指定条件的节点,返回表头节点,删除失败,返回NULL(失败的原因不存在该节点)
DbNode *DeleteNode(DbNode *head, int data) {	//参数1是链表的表头节点
	DbNode *ptmp = NULL;					//参数2是要插入的节点,其数据为data
	//
	DbNode *pnode = FindNode(head, data);	//查找节点
	if (pnode == NULL) {		//节点不存在,返回NULL
		return NULL;
	}
	else if (pnode->left = NULL) {		//node为第一个节点
		head = pnode->right;
		if (head != NULL) {		//链表不为空
			head->left = NULL;
		}
	}
	else if (pnode->right == NULL) {	//node为最后一个节点
		pnode->left->right = NULL;
	}
	else {								//node为中间节点
		pnode->left->right = pnode->right;
		pnode->right->left = pnode->left;
	}
	//释放已被删除的节点空间
	free(pnode);
	return head;
}

19、面试题19

实现有序双向循环链表的插入操作

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

//结构体和函数声明
typedef struct DbNode {
	int data;	//节点数据
	DbNode *left;	 //前驱节点指针
	DbNode *right;	//后继节点指针
}DbNode;

//根据 数据 创建节点,包括头节点和一般节点。创建头节点时,left 和 right 指针都指向本身来形成环状
DbNode *CreateNode(int data) {
	DbNode *pnode = (DbNode *)malloc(sizeof(DbNode));
	pnode->data = data;
	pnode->left = pnode;
	pnode->right = pnode;
	return pnode;
}
//插入新节点,总是在表尾插入;返回表头节点
DbNode *AppendNode(DbNode *head, int data) {	//参数1是链表的表头结点
	DbNode *node = CreateNode(data);	//参数2是要插入的节点,其数据为data。创建数据为data的新节点
	DbNode *p = NULL;
	DbNode *q = NULL;
	//
	if (head == NULL) {
		return NULL;
	}
	//
	q = p = head->right;
	while (p != head) {
		q = p;
		p = p->right;
	}
	//
	q->right = node;	//与原最后一个节点互连
	node->left = q;
	node->right = head;		//与头节点互连,形成环状
	head->left = node;
	return head;
}
//打印整个链表,由于属于循环链表,因此遍历回到head节点时结束打印
void PrintList(DbNode *head) {
	DbNode *pnode = NULL;
	//
	if (head == NULL) {
		return;
	}
	printf("%d", head->data);
	pnode = head ->right;
	while (pnode != NULL) {
		printf("%d", pnode->data);
		pnode = pnode->right;
	}
	printf("\n");
}
//插入一个有序链表(从小到大),返回表头。这里插入位置分为表头和非表头两种情况。
//如果是表头,返回的指针是新的节点,否则返回的是原来的表头节点
DbNode *InsertNode(DbNode *head, int data) {
	DbNode *p = NULL;
	DbNode *q = NULL;
	DbNode *node = NULL;
	//创建数据节点
	node = CreateNode(data);
	//空链表,返回新建链表
	if (head == NULL) {
		head = node;
		return node;
	}
	//data小于表头数据,插入到表头之前
	if (head->data > data) {	//把新建节点作为表头
		head->left->right = node;	//末节点后继指针指向node
		node->left = head->left;	//node的前驱指针指向末节点
		node->right = head;		//node的后继指针指向head
		head->left = node;		//head的前驱指针指向node
		return node;
	}
	//
	p = head->right;
	while (p->data <= data && p != head) {
		p = p->right;
	}
	//
	p = p->left;
	q = p->right;
	//把node插入p和q之间
	p->right = node;
	node->left = p;
	node->right = q;
	q->left = node;
	return head;
}

int main() {
	DbNode *head = CreateNode(1);
	AppendNode(head, 3);
	AppendNode(head, 6);
	AppendNode(head, 8);
	PrintList(head);
	head = InsertNode(head, 0);
	PrintList(head);
	head = InsertNode(head, 4);
	PrintList(head);
	head = InsertNode(head, 10);
	PrintList(head);
	//
	system("pause");
	return 0;
}


20、面试题20

删除两个双向循环链表的相同结果

有两个双向循环链表A,B,知道其头指针分别为 pHeadA,pHeadB。请写一个函数,将两链表中 data 值相同的节点删除

解析

  1. 把 A 中含有的与 B 中相同的数据节点抽出来,组成一个新的链表,如
    链表A:1 2 3 4 2 6 4
    链表B:10 20 3 4 2 10
    新建链表C:2 3 4
  2. 遍历链表 C,删除 A 和 B 的所有节点。如果 A 含有数据相等的节点(A 有两个 4),且 B 也含有此数据节点(B 也有4),则 A 中数据相等的节点全部被删除。
这个代码还未理解透,稍后补吧!

21、面试题21

编程实现队列的入队、出队、测长、打印
队列的实现可使用链表和数组,我们本题中使用单链表来实现队列

#include <stdlib.h>
typedef struct _Node{
	int data;
	struct _Node *next;	//指向链表下一个指针
}node;

typedef struct _Queue {
	node *front;	//对头
	node *rear;		//队尾
}MyQueue;

构造空的队列

//构造一个空的队列
MyQueue *CreateMyQueue() {
	MyQueue *q = (MyQueue *)malloc(sizeof(MyQueue));
	q->front = NULL;	//队首置空
	q->rear = NULL;		//队尾指针置空
	return q;
}

入队、出队、测长、打印

#include <stdlib.h>
#include <stdio.h>
using namespace std;

typedef struct _Node{
	int data;
	struct _Node *next;	//指向链表下一个指针
}node;

typedef struct _Queue {
	node *front;	//对头
	node *rear;		//队尾
}MyQueue;


//构造一个空的队列
MyQueue *CreateMyQueue() {
	MyQueue *q = (MyQueue *)malloc(sizeof(MyQueue));
	q->front = NULL;	//队首置空
	q->rear = NULL;		//队尾指针置空
	return q;
}

//入队,从队尾一端插入节点
MyQueue *enqueue(MyQueue *q, int data) {
	node *newP = NULL;
	newP = (node*)malloc(sizeof(node));	//新建节点
	newP->data = data;	//复制节点数据
	newP->next = NULL;
	//如果队列为空,则新节点既是队首也是队尾
	if (q->rear == NULL) {
		q->front = q->rear = newP;
	}
	else {		//若队列不为空,则新节点放到队尾,队尾指针指向新节点
		q->rear->next = newP;
		q->rear = newP;
	}
	return q;
}

//出队,从队头一端删除节点
MyQueue *dequence(MyQueue *q) {
	node *pnode = NULL;
	pnode = q->front;	//指向队头
	//判断队是否为空
	if (pnode == NULL) {
		printf("Empty queue !\n");
	}
	else {	//新队头
		q->front = q->front->next;
		//当删除后队列为空时,对rear置空
		if (q->front == NULL) {
			q->rear = NULL;
		}
		free(pnode);	//删除原队头节点
	}
	return q;
}

//测长
int GetLength(MyQueue *q) {
	int nlen = 0;
	node *pnode = q->front;	//指向队头
	//队列不为空
	if (pnode != NULL) {
		nlen = 1;
	}
	//遍历队列
	while (pnode != q->rear) {  //队尾不一定是一个链表的末节点
		pnode = pnode->next;	//循环一次,nlen递增1
		nlen++;
	}
	return nlen;
}

//打印
void PrintMyQueue(MyQueue *q) {
	node *pnode = q->front;
	//队列为空
	if (pnode == NULL) {
		printf("Empty Queue!\n");
		return;
	}
	printf("data: ");
	//遍历队列
	while (pnode != q->rear) {
		printf("%d", pnode->data);	//打印节点数据
		pnode = pnode->next;
	}
	printf("%d", pnode->data);	//打印队尾节点数据
}


22、面试题22

队列和栈的区别
队列:入队和出队;队列在队尾入队,队头出队;FIFO,先进先出
栈:入栈和出栈;入栈和出栈都在栈顶,无法对栈底进行操作;LIFO,后进先出;


24、面试题24

使用单链表实现栈

class MyStack {
public:
	MyStack() :top(NULL) {}	//默认构造函数
	void push(MyData data);	 //进栈
	void pop(MyData *pData);	 //出栈
	bool IsEmpty();	 //是否为空栈
	MyData *top;	//栈顶
};

class MyData {
public:
	MyData():data(0),next(NULL){}	//默认构造函数
	MyData(int value):data(value),next(NULL){}	//带参数的构造函数
	int data;	//数据域
	MyData *next;		//下一个节点
};
//进栈
void MyStack::push(MyData data) {
	MyData *pData = NULL;
	pData = new MyData(data.data);	//生成新的节点
	pData->next = top;		//与原来的栈顶节点相连
	top = pData;	//栈顶节点为新加入的节点
}
//判断栈是否为空栈
bool MyStack::IsEmpty() {	//如果 top  为空,则返回1,否则返回0
	return (top == NULL);
}
//出栈,返回栈顶节点内容
void MyStack::pop(MyData *data) {
	if (IsEmpty()) {	//如果为空,则直接返回
		return;
	}
	data->data = top->data;	 //给传出的参数赋值
	MyData *p = top;	//临时保存原栈顶节点
	top = top->next;	//移动栈顶,指向下一个节点
	delete p;	//释放原栈顶节点内存
}

使用栈实现队列
stack:先进后出
queue:先进先出


#include <stdio.h>
#include <iostream>
using namespace std;

class MyStack {
public:
	MyStack() :top(NULL) {}	//默认构造函数
	void push(MyData data);	 //进栈
	void pop(MyData *pData);	 //出栈
	bool IsEmpty();	 //是否为空栈
	MyData *top;	//栈顶
};

class MyData {
public:
	MyData():data(0),next(NULL){}	//默认构造函数
	MyData(int value):data(0),next(NULL){}	//带参数的构造函数
	int data;	//数据域
	MyData *next;		//下一个节点
};
//
class MyQueue {
public:
	void enqueue(MyData data);		//入队
	void dequeue(MyData &data);		//出队
	bool IsEmpty();	 //是否为空
private:
	MyStack s1;		//用于入队
	MyStack s2;		//用于出队
};
//入队
void MyQueue::enqueue(MyData data) {
	s1.push(data);	//只对s1进行栈操作
}
//出队
void MyQueue::dequeue(MyData &data) {
	MyData temp(0);	//局部变量,用于临时存储
	//
	if (s2.IsEmpty()) {		//如果s2为空,把s1的所有元素push到S2中
		while (!s1.IsEmpty()) {
			s1.pop(temp);	//弹出s1的元素
			s2.push(temp);	//压入S2中
		}
	}
	if (!s2.IsEmpty()) {	//此时如果S2不为空,则弹出s2的栈顶元素
		s2.pop(data);
	}
}

//队列判空
bool MyQueue::IsEmpty() {
	return (s1.IsEmpty() && s2.IsEmpty());
}

25、面试题25

用 C++ 实现一个二叉排序树
二叉排序树(Binary Sort Tree):二叉查找树(Binary Search Tree)。满足

  1. 若它的左子树为空,则左子树上所有节点值均小于根节点的值
  2. 若它的右子树非空,则它右子树上所有节点的值均大于根节点的值
  3. 左、右子树本身又是一个二叉排序树
#include <stdio.h>
//节点类定义
class Node {
public:
	int data;		//数据
	Node *parent;	//父亲节点
	Node *left;		//左节点
	Node *right;	//右节点
public:
	Node() :data(-1), parent(NULL), left(NULL), right(NULL) {};
	Node(int num) :data(num), parent(NULL), left(NULL), right(NULL) {};
};
//二叉排序树的定义
class Tree {
public:
	Tree(int num[], int len);		//插入num数组前len个数据,根据int数组创建二叉排序树
	void insertNode1(int data);		//插入节点,递归方法
	void insertNode(int data);		//插入节点,非递归方法
	Node *searchNode(int data);		//查找节点
	void deleteNode(int data);		//删除节点以及子树
private:
	void insertNode(Node* current, int data);	//递归插入方法
	Node *searchNode(Node* current, int data);	//递归查找方法
	void deleteNode(Node* current);		//递归删除方法
private:
	Node* root;		//二叉排序树的根节点
};

下面着重介绍每个函数
创建二叉排序树:首先生成根节点,然后循环调用插入节点的方法对二叉树进行插入操作。

#include <stdio.h>
//节点类定义
class Node {
public:
	int data;		//数据
	Node *parent;	//父亲节点
	Node *left;		//左节点
	Node *right;	//右节点
public:
	Node() :data(-1), parent(NULL), left(NULL), right(NULL) {};
	Node(int num) :data(num), parent(NULL), left(NULL), right(NULL) {};
};
//二叉排序树的定义
class Tree {
public:
	Tree(int num[], int len);		//插入num数组前len个数据
	void insertNode1(int data);		//插入节点,递归方法
	void insertNode(int data);		//插入节点,非递归方法
	Node *searchNode(int data);		//查找节点
	void deleteNode(int data);		//删除节点以及子树
private:
	void insertNode(Node* current, int data);	//递归插入方法
	Node *searchNode(Node* current, int data);	//递归查找方法
	void deleteNode(Node* current);		//递归删除方法
private:
	Node* root;		//二叉排序树的根节点
};

//插入num数组的前 len 个数据
Tree::Tree(int num[], int len) {
	root = new Node(num[0]);	//建立根节点root
	//把数组中的其他数组插入到二叉排序树中
	for (int i = 1; i < len; i++) {
		insertNode1(num[i]);
	}
}

使用非递归方法插入节点:

#include <stdio.h>
//节点类定义
class Node {
public:
	int data;		//数据
	Node *parent;	//父亲节点
	Node *left;		//左节点
	Node *right;	//右节点
public:
	Node() :data(-1), parent(NULL), left(NULL), right(NULL) {};
	Node(int num) :data(num), parent(NULL), left(NULL), right(NULL) {};
};
//二叉排序树的定义
class Tree {
public:
	Tree(int num[], int len);		//插入num数组前len个数据
	void insertNode1(int data);		//插入节点,递归方法
	void insertNode(int data);		//插入节点,非递归方法
	Node *searchNode(int data);		//查找节点
	void deleteNode(int data);		//删除节点以及子树
private:
	void insertNode(Node* current, int data);	//递归插入方法
	Node *searchNode(Node* current, int data);	//递归查找方法
	void deleteNode(Node* current);		//递归删除方法
private:
	Node* root;		//二叉排序树的根节点
};

//插入节点的操作
//非递归方法,插入参数为 data 的节点
void Tree::insertNode1(int data) {
	Node *p, *par;
	Node *newNode = new Node(data);	//创建节点
	//查找插入在哪个节点下面
	p = par = root;
	while (p != NULL) {
		par = p;	//保存节点
		if (data > p->data)		//如果data大于当前节点的data
			p = p->right;		//下一步到左子节点,否则到右子节点
		else if (data < p->data)
			p = p->left;
		else if (data == p->data) {		//不插入重复数据
			delete newNode;
			return;
		}
	}
	newNode->parent = par;
	if (par->data > newNode->data) {	//把新节点插入到目标节点的正确位置
		par->left = newNode;
	}
	else
		par->right = newNode;
}

使用递归方法插入节点:

#include <stdio.h>
//节点类定义
class Node {
public:
	int data;		//数据
	Node *parent;	//父亲节点
	Node *left;		//左节点
	Node *right;	//右节点
public:
	Node() :data(-1), parent(NULL), left(NULL), right(NULL) {};
	Node(int num) :data(num), parent(NULL), left(NULL), right(NULL) {};
};
//二叉排序树的定义
class Tree {
public:
	Tree(int num[], int len);		//插入num数组前len个数据
	void insertNode1(int data);		//插入节点,递归方法
	void insertNode(int data);		//插入节点,非递归方法
	Node *searchNode(int data);		//查找节点
	void deleteNode(int data);		//删除节点以及子树
private:
	void insertNode(Node* current, int data);	//递归插入方法
	Node *searchNode(Node* current, int data);	//递归查找方法
	void deleteNode(Node* current);		//递归删除方法
private:
	Node* root;		//二叉排序树的根节点
};
//insertNode()使用递归方法
void Tree::insertNode(int data) {
	if (root != NULL) {
		insertNode(root, data);	//调用递归插入方法
	}
}

//递归插入方法
void Tree::insertNode(Node* current, int data) {
	//如果data小于当前节点数据,则在当前节点的左子树插入
	if (data < current->data) {
		//如果左节点不存在,则新建插入到左节点
		if (current->left == NULL) {
			current->left = new Node(data);
			current->left->parent = current;
		}
		else {
			insertNode(current->left, data);	//否则对左节点进行递归调用
		}
	}
	//如果data大于当前节点数据,则在当前节点的右子树插入
	else if (data > current->data) {
		//如果右子树不存在,则插入到右子树
		if (current->right == NULL) {
			current->right = new Node(data);
			current->right->parent = current;
		}
		else {	//如果右子树存在,就对右节点进行递归调用
			insertNode(current->right, data);
		}
		return;	//data 等于当前节点数据时,不插入
	}
	return;
}

查找节点

//递归查找方法
Node* Tree::searchNode(Node* current, int data) {
	//如果data小于当前节点数据,则递归搜索其左子树
	if (data < current->data) {
		if (current->left == NULL) {	//如果不存在左子树,则返回NULL
			return NULL;
		}
		return searchNode(current->left, data);
	}
	//如果data大于当前节点数据,则递归搜索其左子树
	else if (data > current->data) {
		//如果不存在右子树,则返回NULL
		if (current->right == NULL) {
			return NULL;
		}
		return searchNode(current->right, data);
	}
	//相等,则返回current
	return current;
}

删除节点

//删除数据为data的节点及其子树
void Tree::deleteNode(int data) {
	Node *current = NULL;
	//查找节点
	current = searchNode(data);
	if (current != NULL) {
		deleteNode(current);	//删除节点及其子树
	}
}

//删除current节点及其子树的所有节点
void Tree::deleteNode(Node *current) {
	if (current->left != NULL) {	//删除左子树
		deleteNode(current->left);
	}
	if (current->right != NULL) {	//删除右子树
		deleteNode(current->right);
	}
	if (current->parent == NULL) {	//如果current是根节点,把root置空
		delete current;
		root = NULL;
		return;
	}
	//将current父亲节点的相应指针置空
	if (current->parent->data > current->data)	//current为其父节点的左子节点
		current->parent->left = NULL;
	else
		current->parent->right = NULL;		//current为其父节点的右子节点
	//最后删除此节点
	delete current;
}

26、面试题26

使用递归和非递归方法实现中序遍历
中序遍历:左节点–>根节点–>右节点

中序遍历,递归解法

//中序遍历,递归解法
void Tree::InOrderTree(Node* current) {
	if (current != NULL) {
		InOrderTree(current->left);
		cout << current->data << " ";
		InOrderTree(current->right);
	}
}

中序遍历,非递归解法

  1. 先将根节点入栈,遍历左子树(左子树全部进栈)
  2. 遍历完一个一个左子树返回时(不是全部出去),栈顶元素应为根节点,此时出栈,并打印节点数据
  3. 再中序遍历右子树
//中序遍历,非递归解法
void Tree::InOrderTreeUnRec(Node *root) {
	stack<Node *> s;
	Node *p = root;	//根节点
	while (p != NULL || !s.empty()) {
		while (p != NULL) {		//遍历左子树
			s.push(p);		//根节点先入栈,再把遍历的节点全部压栈
			p = p->left;
		}
		//
		if (!s.empty()) {
			p = s.top();	//得到栈顶内容
			s.pop();	//出栈
			cout << p->data << " ";		//打印
			p = p->right;	//指向右子节点,下一次循环就会遍历右子树
		}
	}
}

27、面试题27

使用递归和非递归方法实现先序遍历
先序遍历:根节点–>左节点–>右节点

递归解法

void Tree::PreOrderTree(Node* current) {
	if (current != NULL) {
		cout << current->data << endl;
		PreOrderTree(current->left);
		PreOrderTree(current->right);
	}
}

非递归解法

void Tree::PreOrderTree(Node* root) {
	stack<Node *> s;
	Node *p = root;
	while (p != NULL || !s.empty()) {
		while (p != NULL) {
			cout << p->data << " ";
			s.push(p);
			p = p->left;
		}
		//
		if (!s.empty()) {
			p = s.top();
			s.pop();
			p = p->right;
		}
	}
}

28、面试题28

使用递归和非递归方法实现后序遍历

递归方法

void Tree::PostOrderTree(Node* current) {
	if (current != NULL) {
		PostOrderTree(current->left);	//遍历左子树
		PostOrderTree(current->right);	//遍历右子树
		cout << current->data << " ";	//打印根节点
	}
}

非递归方法
假设T为要遍历树的根指针,后序遍历要求在遍历完左、右子树后再访问根。需要判断根节点的左、右子树是否均遍历过。

我们采用标记法,节点入栈时候,需要一个标志 tag 一同入栈(tag 为 0 表示遍历左子树前的现场保护,tag 为1表示右子树前的现场保护)。

首先将 T 和 tag (为0)入栈,遍历左子树;返回时,修改栈顶 tag 为1,遍历右子树,最后访问根节点。

void Tree::PostOrderTreeUnRec(Node* root) {
	stack<Node *>s;
	Node *p = root;
	//
	while (p != NULL || !s.empty()) {
		while (p != NULL) {
			s.push(p);	//根节点入栈
			p = p->left;		//左子树入栈
		}
		//
		if (!s.empty()) {
			p = s.top();	//得到栈顶元素

			if (p->tag) {	//tag为1时
				cout << p->data << " ";	//打印节点数据
				s.pop();	//出栈
				p = NULL;	//第二次访问标志其右子树已经遍历
			}
			else {
				p->tag = 1;	//修改tag为1
				p = p->right;	//指向右节点,下次遍历其左子树
			}
		}
	}
}

29、面试题29

层次遍历算法
用队列实现,出队

void Tree::LevelOrderTree(Node* root) {
	queue<Node *>q;	 //定义队列q,它的元素为Node* 型指针
	Node *ptr = NULL;

	q.push(root);
	while (!q.empty()) {
		ptr = q.front();  //根节点入队
		q.pop();	//出队
		cout << ptr->data << " ";
		if (ptr->left != NULL) {
			q.push(ptr->left);
		}
		if (ptr->right != NULL) {
			q.push(ptr->right);
		}
	}
}

28、面试题28

判断给定的二叉树是否是二叉排序树
中序遍历的结果就是排序二叉树的排序输出
这段程序的核心就是:使用一个 lastvalue 来记录上一个节点的数据。如果出现 lastvalue 大于或者等于当前出队的节点值,则返回 false;否则一直入队和出队。如果 lastValue 始终小于当前出队的节点值,则是升序排序,返回 true

//使用中序遍历二叉树是否为排序二叉树
bool IsSortedTree(Tree tree) {
	int lastValue = 0;
	stack<Node *>s;
	Node *p = tree.root;
	//
	while (p != NULL || !s.empty()) {
		while (p != NULL) {
			s.push(p);		//遍历左子树
			p = p->left;	//把遍历的左节点全部压栈
		}
		//
		if (!s.empty()) {
			p = s.top();	//得到栈顶内容
			s.pop();	//出栈
			if (lastValue == 0 || lastValue < p->data) {
				lastValue = p->data;	//如果是第一次弹出或者lastvalue小于
			}
			else if (lastValue >= p->data) {
				//如果lastvalue大于当前节点值,则返回false
				return false;
			}
			p = p->right;	//指向右子节点,下一次循环就会中序遍历右子树
		}
	}
	return true;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值