一、数组(最简单的数据结构)
定义:占据一块连续内存并按照顺序存储数据。创建时先指定大小,分配内存。
优点:时间效率高。实现简单的hash(下标为key,对应的数据为value)
缺点:空间效率差。如果我们只在数组中存一个数字,也先分配所有的内存。
扩展:动态数组(解决空间效率差),手段:扩容后,复制内容到新的数组,释放之前的内存。时间性能变差,因此,要尽量减少改变数组容量的次数。
数组与指针:声明一个数组时,数组名字也是一个指针,指向数组的第一个元素。
例题:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样一个二维数组和一个整数,判断数组中是否含有整数。
思路:
写一个bool Find(int* matrix, int rows, int columns, int number)函数,将一个矩阵的右上角的值a和这个整数b进行对比.
if(a==b) return true;
else if (a>b) 去掉当前列,在剩下的列中进行查找。
else 去掉当前行,在剩下的行中进行查找。
#include <iostream> using namespace std; bool Find(int* matrix, int rows, int columns, int number) { bool found = false; if(matrix != NULL&& rows > 0 && columns > 0) { int row = 0; int column = columns - 1; while(row < rows && column >=0) { if(matrix[row * columns + column] == number) { found = true; break; } else if(matrix[row * columns + column] > number) -- column; else ++ row; } } return found; } //========测试代码============= void Test(char * testName, int * matrix, int rows, int columns, int number, bool expected) { if(testName != NULL) printf("%s begins: ",testName); bool result=Find(matrix, rows, columns, number); if(result == expected) printf("Passed.\n"); else printf("Failed.\n"); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数在数组中 void Test1() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test1", (int*)matrix, 4, 4, 7, true); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数不在数组中 void Test2() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test2", (int*)matrix, 4, 4, 5, false); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数是数组中最小的数字 void Test3() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test3", (int*)matrix, 4, 4, 1, true); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数是数组中最大的数字 void Test4() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test4", (int*)matrix, 4, 4, 15, true); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数比数组中最小的数字还小 void Test5() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test5", (int*)matrix, 4, 4, 0, false); } // 1 2 8 9 // 2 4 9 12 // 4 7 10 13 // 6 8 11 15 // 要查找的数比数组中最大的数字还大 void Test6() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; Test("Test6", (int*)matrix, 4, 4, 16, false); } // 鲁棒性测试,输入空指针 void Test7() { Test("Test7", NULL, 0, 0, 16, false); } void main() { Test1(); Test2(); Test3(); Test4(); Test5(); Test6(); Test7(); }
二.字符串
定义:若干字符组成的序列
c++/c与Java中字符串的对比:c/c++中每个字符串都是以‘\0‘作为结尾;Java中却不是这样的,Java中的一切都是对象,对象有长度,编译器可以确定输出的字符个数。
题目:请实现一个函数,把字符串中的每一个空格替换成‘%20‘,例如,输入为"we are happy",则输出为:"we%20are%20happy"
思路:要弄清楚两点:第一,在原串上面干,字符串的长度变长。第二,在新串上面干,自己分配足够地内存。
从头开始遍历字符串,在插入字符后,后面的字符会后移。因此我们采用从后面开始遍历。
先遍历一次字符串,统计字符串中空格的总数,计算替换后字符串的总长度。用指针p1和p2分别指向原字符串的尾部和替换后字符串的尾部。
#include <iostream> using namespace std; void ReplaceBlank(char string[],int length) { if(string==NULL||length<=0) return ; int originalLength=0; int numberOfBlank=0; int i=0; while(string[i]!=‘\0‘) { ++ originalLength; if(string[i]==‘ ‘) ++numberOfBlank; ++i; } int newLength=originalLength+numberOfBlank*2; if(newLength>length) { return; } int indexOfOriginal=originalLength; int indexOfNew=newLength; while(indexOfOriginal>=0&&indexOfOriginal<indexOfNew) { if(string[indexOfOriginal]==‘ ‘) { string[indexOfNew--]=‘0‘; string[indexOfNew--]=‘2‘; string[indexOfNew--]=‘%‘; } else string[indexOfNew--]=string[indexOfOriginal]; --indexOfOriginal; } } void Test(char* testName, char string[], int length, char expected[]) { if(testName != NULL) printf("%s begins: ", testName); ReplaceBlank(string, length); if(expected == NULL && string == NULL) printf("passed.\n"); else if(expected == NULL && string != NULL) printf("failed.\n"); else if(strcmp(string, expected) == 0) printf("passed.\n"); else printf("failed.\n"); } // 空格在句子中间 void Test1() { const int length = 100; char string[length] = "hello world"; Test("Test1", string, length, "hello%20world"); } // 空格在句子开头 void Test2() { const int length = 100; char string[length] = " helloworld"; Test("Test2", string, length, "%20helloworld"); } // 空格在句子末尾 void Test3() { const int length = 100; char string[length] = "helloworld "; Test("Test3", string, length, "helloworld%20"); } // 连续有两个空格 void Test4() { const int length = 100; char string[length] = "hello world"; Test("Test4", string, length, "hello%20%20world"); } // 传入NULL void Test5() { Test("Test5", NULL, 0, NULL); } // 传入内容为空的字符串 void Test6() { const int length = 100; char string[length] = ""; Test("Test6", string, length, ""); } //传入内容为一个空格的字符串 void Test7() { const int length = 100; char string[length] = " "; Test("Test7", string, length, "%20"); } // 传入的字符串没有空格 void Test8() { const int length = 100; char string[length] = "helloworld"; Test("Test8", string, length, "helloworld"); } // 传入的字符串全是空格 void Test9() { const int length = 100; char string[length] = " "; Test("Test9", string, length, "%20%20%20"); } int main(int argc, char* argv[]) { Test1(); Test2(); Test3(); Test4(); Test5(); Test6(); Test7(); Test8(); Test9(); return 0; }
相关例题:有两个排序的数组A1和A2,内存在A1的末尾有足够的多余的空间来容纳A2,请实现一个函数,把A2中地所有的数字插入到A1中并且所有的数字是排序的。
思路:同样是定义两个指针p1和p2,分别指向字数组A1和A2尾端,sp1和sp2,分别指向数组A1和A2的头部。并且设置一个指针p3指向合并后的字符串(即:A1数组)的尾端。
当满足p1>sp1和p2>sp2的同时。if(*p1>=*p2)*p3=*p1;else *p3=*p2;之后,当满足p1>sp1,*p3=*p1;当满足p2>sp2,*p3=*p2。
//合并数组
#include <stdio.h>
void mergaMatrix(int* matrix1,int* matrix2,
int lenofmtrx1,int lenofmtrx2,int sizeofmatrix1)
{
if(sizeofmatrix1 != 0 && matrix1 != NULL && lenofmtrx1 !=0
&& matrix2 != NULL && lenofmtrx2 != 0 )
{
int* pNewMatrix1 = matrix1 + lenofmtrx1 + lenofmtrx2 -1;
int* pMatrix1 = matrix1 + lenofmtrx1 - 1;
int* pMatrix2 = matrix2 +lenofmtrx2 - 1;
while(pMatrix1 >= matrix1 && pMatrix2 >= matrix2)
{
if(*pMatrix1 >= *pMatrix2)
*pNewMatrix1-- = *pMatrix1--;
else
*pNewMatrix1-- = *pMatrix2--;
}
while(pMatrix1 >= matrix1)
{
*pNewMatrix1-- = *pMatrix1--;
}
while(pMatrix2 >= matrix2)
{
*pNewMatrix1-- = *pMatrix2--;
}
}
return;
}
//单元测试
void test(int* matrix1,int* matrix2,
int lenofmtrx1,int lenofmtrx2,int sizeofmatrix1)
{
if(matrix1 != NULL)
{
for( int i=0; i<lenofmtrx1;i++)
{
printf("%d ",*(matrix1+i));
}
}
printf("\n");
if(matrix2 != NULL){
for( int i=0; i<lenofmtrx2;i++)
{
printf("%d ",*(matrix2+i));
}
}
printf("\n");
mergaMatrix(matrix1,matrix2,lenofmtrx1,lenofmtrx2,sizeofmatrix1);
for( int i=0; i<lenofmtrx1+lenofmtrx2;i++)
{
printf("%d ",*(matrix1+i));
}
printf("\n");
}
//一般情况
void test1()
{
const int sizeofmatrix1 = 100;
int lenofmtrx1 = 3;
int matrix1[sizeofmatrix1] = {1,3,5};
int lenofmtrx2 = 4;
int matrix2[] = {2,4,6,8};
test(matrix1,matrix2,lenofmtrx1,lenofmtrx2,sizeofmatrix1);
}
//其中一个数组的书全部小于另外一个
void test2()
{
const int sizeofmatrix1 = 100;
int lenofmtrx1 = 3;
int matrix1[sizeofmatrix1] = {1,3,5};
int lenofmtrx2 = 4;
int matrix2[] = {6,7,8,9};
test(matrix1,matrix2,lenofmtrx1,lenofmtrx2,sizeofmatrix1);
}
//其中一个为空
void test3()
{
const int sizeofmatrix1 = 100;
int lenofmtrx1 = 3;
int matrix1[sizeofmatrix1] = {1,3,5};
test(matrix1,NULL,lenofmtrx1,0,sizeofmatrix1);
}
//两个都为空
void test4()
{
const int sizeofmatrix1 = 100;
test(NULL,NULL,0,0,sizeofmatrix1);
}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}
三.链表(创建,尾部添加,删除,打印(从头到尾),打印(从尾到头(stack),打印从尾到头(递归(会导致栈溢出))面试中最频繁的数据结构。
定义:由指针把若干个节点链接成链状的结构。
例题:从尾到头打印一个链表。
思路:
1.创建链表返回头指针ListNode* createLink(int a[],int k)(初始化头指针Head和行动指针p,当满足当前长度小于链表的长度时。创建一个节点pNew并赋初值,if(Head==NULL){Head=pNew;p=pNew;}else{p->next=pNew;p=p->next;})。
2.尾部添加无返回值void addTail(ListNode** Head,int value)(用到了指针的指针ListNode**head,创建一个节点pNew并赋初值。if(*Head==NULL){*Head=pNew;else创建行动指针p并指向头结点,找到插入节点的头一个接点,在它的后面插入节点。})。
3.删除一个已知值的节点返回空,void removeNode(ListNode** Head,int value)(同样用到指针的指针,if(Head==NULL&&*Head==NULL)初始化要删除的指针toBeDelete,if(*Head==value){toBeDelete=*Head,*Head=*Head->next)},else,初始化行动指针p并指向头指针,找到要删除的节点的头一个节点p,if(p->next!=null&&p->next->value==value){ toBeDelete=p->next,p-next=p->next->next}。)
4.打印(从头到尾)返回空,void printLink(ListNode* pHead)(初始化行动指针p并指向头节点,当满足p不为空的时候,输出p->value,并且移动p=p->next)
5.打印(从尾到头(栈))返回空,void printLinkReservese(ListNode* pHead)(定义一个栈 std::stack<ListNode *>nodes,初始化行为指针p并指向头节点,当满足p不为空时,将nodes.push(p),并且移动p=p->next;当nodes.empty不为空时,将行为指针指向顶并且输出p->value,nodes.pop();)
6.打印(从尾到头(递归)),返回空。void printLinkReservese_Recurse(ListNode *pHead)(if(pHead!=NULL){if(pHead->next!=NULL) printLinkReservse_Reservese_Recurse(pHead->next);}输出pHead->value.)
#include<iostream.h> #include <stdio.h> #include <stack> struct ListNode { int m_nValue; ListNode* m_pNext; }; void RemoveNode(ListNode ** pHead,int value) { if(pHead==NULL||*pHead==NULL) return; ListNode* toBeDelete=NULL; if((*pHead)->m_nValue==value) { toBeDelete=*pHead; *pHead=(*pHead)->m_pNext; } else { ListNode*p=*pHead; while(p->m_pNext!=NULL&&p->m_pNext->m_nValue!=value) p=p->m_pNext; if(p->m_pNext!=NULL&&p->m_pNext->m_nValue==value) { toBeDelete=p->m_pNext; p->m_pNext=p->m_pNext->m_pNext; } //这一步很重要,防止操作已经释放的内存空间 if(toBeDelete!=NULL) { delete toBeDelete; toBeDelete=NULL; } } } void AddToTail(ListNode ** pHead,int value) { ListNode * pNew=new ListNode(); pNew->m_nValue=value; pNew->m_pNext=NULL; if(*pHead==NULL) { *pHead=pNew; } else { ListNode * pNode = *pHead; while(pNode->m_pNext != NULL) pNode=pNode->m_pNext; pNode->m_pNext=pNew; } } ListNode * CreateLink(int a[],int k) { ListNode * Head=NULL,*q=NULL; for(int i=0;i<k;i++) { ListNode * pNew=new ListNode(); pNew->m_nValue=a[i]; pNew->m_pNext=NULL; if(Head==NULL) { Head=pNew; q=pNew; } else { q->m_pNext=pNew; q=q->m_pNext; } } return Head; } //从头到尾打印列表 void printLink(ListNode * pHead) { ListNode *p=pHead; while(p) { cout<<p->m_nValue<<" "; p=p->m_pNext; } cout<<endl; } //从尾到头打印链表(用栈)。 void PrintListReversesingly_Iteratively(ListNode * pHead) { std::stack<ListNode *> nodes; ListNode *p=pHead; while(p) { nodes.push(p); p=p->m_pNext; } while(!nodes.empty()) { p=nodes.top(); cout<<p->m_nValue<<" "; nodes.pop(); } } //从尾到头打印链表(用递归,隐式调用栈)。 void printListReversesingly_Recursively(ListNode * pHead) { if(pHead!=NULL) { if(pHead->m_pNext!=NULL) { printListReversesingly_Recursively(pHead->m_pNext); } } cout<<pHead->m_nValue<<" "; } void main() { int a[]={1,2,3}; ListNode * Head=CreateLink(a,3); printLink(Head); AddToTail(&Head,6); printLink(Head); printListReversesingly_Recursively(Head); cout<<endl; RemoveNode(&Head,3); printLink(Head); PrintListReversesingly_Iteratively(Head); cout<<endl; }
四.树(二叉树(创建,打印,删除))
定义:除了根节点之外,每个结点都有一个父节点,除了叶子节点外所有的节点都有一个或者多个子节点。
二叉树:每个节点最多有两个叶子节点
遍历:按照某个顺序访问树中的所有节点。
三种常见的遍历:前序遍历,中序遍历,后续遍历(可以用递归和循环两种方式实现)
可实现的题目:二叉树的深度,树的子结构,二叉树的后续遍历。从上到下遍历二叉树(宽度优先遍历)。
二查搜索树:左子结点总是小于等于根节点,而右子节点总是大于等于根节点。找到一个节点的平均时间为O(logn)
二叉树特例:堆和红黑二叉树堆:最大堆和最小堆。可以解决(快速找最大值和最小值问题)
红黑树:将树中的节点定义为红、黑两种颜色,通过规则确保从根节点到叶子节点的最长路径不超过最短路径的两倍。在C++ 中的STL(Standard Template libary(标准模板库),set,multiset,map,multimap等数据结构都是基于红黑树实现的。)
重建二叉树
题目:输入某个二叉树的前序遍历和中序遍历的结果,重建该二叉树,假设输入前序和中序遍历的结果中没有重复的数字。例如:输出得 前序遍历为{1,2,4,7,3,5,6,8}和中序遍历{4,7,2,1,5,3,8,6},重建二叉树。
思路:
1.创建二叉树,返回值为BinaryTreeNode *,BinaryTreeNode* construct(int* preorder,int* inorder,int length)(如果前序遍历和中序遍历的头指针都不空,及length大于0,则调用函数BinaryTreeNode *ConstructCore(int*startPreorder,int*endPreorder,int*startInorder,int*endInorder)(首先将前序遍历的第一个值赋值给rootValue,初始化根节点,if(startPreorder==endPreorder){if(startInorder==endInorder&&*startPreorder==*startInorder) return root;else 抛空异常。) 据中序遍历找到根节点。定义一个行动指针指p向中序遍历的头结点。当满足p<=endInorder时,找到根节点(root),分别求出左、右子树的长度leftLength和rightLength,当leftLength和rightLength不为零时,分别创建左右子树。root->left=调用ConstructCore函数,root->right=调用ConstructCore函数。
2.打印二叉树。无返回值。首先创建要打印一个节点的函数void printTreeNode(BinaryTreeNode* pNode)(当pNode不为空时,打印pNode的值,如果pNode->left为空,打印pNode->left->value,右指针同左指针一样操作。else 抛空。),然后再创建一个函数,返回值为空。void PrintTree(BinaryTreeNode *root){如果根节点不 为空打印根节点的值,左指针不为空,递归打印左子树。右指针同理。}。
3.删除二叉树。无返回值。函数void DeleteNode(BinaryTreeNode* pRoot){如果pRoot不为空,则将左右指针分别赋值给一个新的指针,然后删除pRoot,并且置空。递归删除左右子树。}
#include <stdio.h> #include <iostream> using namespace std; struct BinaryTreeNode { int m_nValue; BinaryTreeNode* m_pLeft; BinaryTreeNode* m_pRight; }; BinaryTreeNode* ConstructCore(int* startPreorder,int* endPreorder,int* startInorder,int* endInorder) { int rootValue=startPreorder[0]; BinaryTreeNode* root=new BinaryTreeNode(); root->m_nValue=rootValue; root->m_pLeft=root->m_pRight=NULL; if(startPreorder==endPreorder) { if(startInorder==endInorder&&*startPreorder==*startInorder) { return root; } else throw std::exception("Invalid put!"); } //通过中序遍历序列找到根节点 int* rootInorder=startInorder; while(rootInorder<=endInorder&&*rootInorder!=rootValue) { ++rootInorder; } if(rootInorder==endInorder&&*rootInorder!=rootValue) { throw std::exception("Invalid put"); } int leftLength=rootInorder-startInorder; int rightLength=endInorder-rootInorder; int* leftPreorderEnd=startPreorder+leftLength; if(leftLength>0) { //递归构建左子树 root->m_pLeft=ConstructCore(startPreorder+1,leftPreorderEnd,startInorder,rootInorder-1); } if(rightLength>0) { //递归构建右子树 root->m_pRight=ConstructCore(leftPreorderEnd+1,endPreorder,rootInorder+1,endInorder); } return root; } BinaryTreeNode* Construct(int* preorder,int* inorder,int length) { if(preorder==NULL||inorder==NULL||length<=0) { throw std::exception("Invalid put!"); } return ConstructCore(preorder,preorder+length-1,inorder,inorder+length-1); } void PrintTreeNode(BinaryTreeNode* pNode) { if(pNode != NULL) { printf("value of this node is: %d\n", pNode->m_nValue); if(pNode->m_pLeft != NULL) printf("value of its left child is: %d.\n", pNode->m_pLeft->m_nValue); else printf("left child is null.\n"); if(pNode->m_pRight != NULL) printf("value of its right child is: %d.\n", pNode->m_pRight->m_nValue); else printf("right child is null.\n"); } else { printf("this node is null.\n"); } printf("\n"); } //递归打印左右子树 void PrintTree(BinaryTreeNode* pRoot) { PrintTreeNode(pRoot); if(pRoot != NULL) { if(pRoot->m_pLeft != NULL) PrintTree(pRoot->m_pLeft); if(pRoot->m_pRight != NULL) PrintTree(pRoot->m_pRight); } } //递归删除左右子树 void DestroyTree(BinaryTreeNode* pRoot) { if(pRoot != NULL) { BinaryTreeNode* pLeft = pRoot->m_pLeft; BinaryTreeNode* pRight = pRoot->m_pRight; delete pRoot; pRoot = NULL; DestroyTree(pLeft); DestroyTree(pRight); } } void main() { const int length = 8; int preorder[length] = {1, 2, 4, 7, 3, 5, 6, 8}; int inorder[length] = {4, 7, 2, 1, 5, 3, 8, 6}; BinaryTreeNode *root = Construct(preorder, inorder, length); PrintTree(root); DestroyTree(root); }
五.栈与队列
栈定义:用来存储函数调用时各个函数的参数,返回地址及临时变量等。
特点:先进后出,不考虑排序。
队列特点:先进先出。
题目:用两个栈实现一个队列,队列申明如下,实现它的两个函数appendTail和deleteHead,即:队列的尾部插入节点,头部删除节点。
思路:自定义一个函数模板CQueue,它有public属性或方法:构造函数,析构函数,在尾部添加节点void appendTail(const T& node),删除队列的头节点T deleteHead().私有属性:stack<T> stack1;stack<T>stack2;由于定义CQueue,为模板的原因,在尾部添加节点实现函数为:template< typename T> void cQueue<T>::appendTail(const T& node){stack1.push(node)},删除队列头部的节点template<T> T cQueue<T> ::deleteHead()如果stack2<=0,当stack1.size()满足大于0时,将stack1,中的所有元素压入stack2.如果stack2==0,抛空。定义一个T类型的head并且赋初值为stack2.top,stack2.pop(),返回Head。
#include <iostream> #include <stack> #include <exception> using namespace std; template <typename T> class CQueue { public: CQueue(void); ~CQueue(void); // 在队列末尾添加一个结点 void appendTail(const T& node); // 删除队列的头结点 T deleteHead(); private: stack<T> stack1; stack<T> stack2; }; //成员函数定义. template <typename T> CQueue<T>::CQueue(void) { } template <typename T> CQueue<T>::~CQueue(void) { } //向队列中插入元素. template <typename T> void CQueue<T>::appendTail(const T& node) { stack1.push(node); } //从队列中删除元素. template <typename T> T CQueue<T>::deleteHead() { //先判断stack2是否为空. //若其不为空,则从中弹出元素; //若为空,则将stack1中元素依次弹出并存入stack2中. if(stack2.size() <= 0) while (stack1.size() > 0) {//当stack2为空时,将stack1中所有元素存入stack2中. T data = stack1.top(); stack1.pop(); stack2.push(data); } if(stack2.size() == 0) throw new exception("queue is empty!"); T head = stack2.top(); stack2.pop(); return head; } //测试函数. void Test(char actual, char expected) { if(actual == expected) cout << "Test passed." << endl; else cout << "Test failed.\n" << endl; } int main() { CQueue<char> queue; queue.appendTail(‘a‘); queue.appendTail(‘b‘); queue.appendTail(‘c‘); char head = queue.deleteHead(); Test(head, ‘a‘); head = queue.deleteHead(); Test(head, ‘b‘); queue.appendTail(‘d‘); head = queue.deleteHead(); Test(head, ‘c‘); queue.appendTail(‘e‘); head = queue.deleteHead(); Test(head, ‘d‘); head = queue.deleteHead(); Test(head, ‘e‘); return 0; }