面试——数据结构
来源:互联网 发布:迫击炮升级数据 编辑:程序博客网 时间:2024/05/21 08:49
1、指针内存错误问题(有关链式存储)
在此之前说一个当时写此实现的一个指针内存错误。
一个指针,不管定义成什么类型,当要去取它的内存模型中的某个东西时,这个指针所指向的内存必须存在,也就是之前给其所指向的内存分配了空间。
当定义:
int *p; *p = 5; //执行这样的操作是错误的,这是简单的,//然而复杂的是如下的,定义一个结构体指针//节点元素typedef struct node1{ int data; struct node1 * next;}node;//栈节点typedef struct stack{ node * btn,*top;}stack;stack *st;//以下的两个操作都是错误的,将导致segment fault!st->btn = st->top = NULL;s->btn = s->top = (node*)malloc(sizeof(node));
因为stack *st;只声明了一个st的指针,系统给其只分配了固定大小(4字节地址大小)的内存,它所指向的内存区域还未分配内存。
【如图分析】
//简单的例子typedef struct test{ int a,b;}test;test t; //此处系统给其分配了一个test结构体大小的内存test *p; //此处系统给其分配了一个指针大小的内存(大部分固定为4字节)t.a = 5; p->a = 10; //执行错误
2、栈的链式存储结构
/** 栈的链式存储结构* top栈顶指针永远指向栈顶元素的下一个位置* btn不变,指向栈底* 栈空的判断为,栈顶 == 栈底* 初始化时,让栈顶和栈底一起指向一块内存(node)* 入栈时:总是先将元素放置在栈顶top指向的位置,然后再后移top* 出栈时:总是先前移top,再取top位置的值*///节点元素typedef struct node1{ int data; struct node1 * next;}node;//栈节点typedef struct stack{ node * btn,*top;}stack;int init_stack(stack *s){ s->btn = s->top = (node*)malloc(sizeof(node)); if(!s->btn) return 0; s->btn->next = s->top->next = NULL; return 1;}//压栈void push(stack *s, int data){ node *q; q = (node *)malloc(sizeof(node)); if(!q) return; q->next = NULL; s->top->data = data; s->top->next = q; s->top = q;}//出栈int pop(stack *s){ node *q,*t; int ret = -1; if(s->top == s->btn) { printf("empty stack!\n"); } else { t = s->top; q = s->btn; while(q->next != t) //寻找top前一个位置的指针 { q=q->next; } ret = q->data; //取top前一位置的值 s->top = q; //s->top = NULL; 此处应该是s->top->next = NULL; free(t); } return ret; }void print_stack(stack *s){ node *q; for(q=s->btn; q!=s->top;q=q->next) { printf("%d\n",q->data); }}int main(){ stack *st; st = (stack *)malloc(sizeof(stack)); init_stack(st); int i; for(i=0;i<5;i++) { push(st,i); } print_stack(st); printf("==========\n"); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); printf("%d\n\n",pop(st)); print_stack(st); return 1;}
3、用两栈实现队列、两队列实现栈
两栈实现队列:
【思路】:
若s2空, 则弹出s1数据到s2中,
弹出s2中的数据
若s2不空, 则直接 弹出s2中的数据
//入队void en_que(int data){ push(s1,data);}//s1专属进队列//自己的思路是:将s1入栈到s2,从s2中出一个数后,又将s2入栈到s1,非常耗时!!int de_que(){ int ret = -1; while(s1->top != s1->btn) push(s2,pop(s1)); if(s2->top == s2->btn) { printf("queue is empty\n"); return ret; } ret = pop(s2); while(s2->top != s2->btn) push(s1,pop(s2)); return ret;}//s1专属进队列//若s2空,则弹出s1数据到s2中,出s2中的数据//若s2不空,则直接弹出s2中的数据int de_que_ex(){ if(s2->top == s2->btn) { while(s1->top != s1->btn) push(s2,pop(s1)); } if(s2->top == s2->btn) { printf("queue is empty\n"); return -1; } else { return pop(s2); }}
两队列实现栈:
在C++ STL中有双向队列deque,当单向的来用就行,设有两个队列A和B,栈的push操作,直接push到A的队尾就行了。栈的pop操作时,将A中的队列依次取出放到B中,取到最后一个时,最后一个不要放到B中,直接删掉,再将B中的值依次放回A中。栈的top操作时,将A中的队列依次取出放到B中,取到最后一个时,将最后一个值记录下来,再将最后一个值放到B中,再将B中的值依次放回到A中。
#include<iostream> #include <deque> using namespace std; template<class T> class Mystack { public: Mystack(){} ~Mystack(){} void push(T t); T top(); void pop(); private: deque<T> A; deque<T> B; }; template<class T> void Mystack<T>::push(T t) { A.push_back(t); } template<class T> T Mystack<T>::top() { while(A.size()>1) { B.push_back(A.front()); A.pop_front(); } T tmp=A.front(); B.push_back(A.front()); A.pop_front(); while(B.size()!=0) { A.push_back(B.front()); B.pop_front(); } return tmp; } template<class T> void Mystack<T>::pop() { while(A.size()>1) { B.push_back(A.front()); A.pop_front(); } A.pop_front(); while(B.size()!=0) { A.push_back(B.front()); B.pop_front(); } }
4、快速排序
【重点】
一次快速排序时,从两边往内移动的两个指针low/high,只需比较low < high
当low==high时,正好比完,而哨兵值(中间值)就应该放在low/high处,记得将low/high返回
//一趟快速排序过程int quick_sort(int *b, int low, int high){ int tmp = b[low]; while(low < high) { while(low < high && b[high] >= tmp) high --; if(low < high) b[low++] = b[high]; while(low < high && b[low] <= tmp) low ++; if(low < high) b[high--] = b[low]; } b[high] = tmp; //重点1 return high; //重点2}//递归快速排序void Q_sort(int *b, int low, int high){ int mid; if(low < high) //重点3 { mid = quick_sort(b,low,high); //重点4 Q_sort(b,0,mid-1); Q_sort(b,mid+1,high); }}void q_sort(int *b, int n){ Q_sort(b,0,n-1);}
5、冒泡排序
【算法思路】
用图表达:
//冒泡排序 从小到大的顺序void bubule(int *b, int n){ int i,j; for(i=n-1;i>0;i--) { for(j=0;j<i;j++) { //if(b[j] > b[i]) //找到一个最大的给b[i],即是从小到大的顺序 if(b[j] > b[j+1]) //从头到i依次比较相邻数的大小 { swap2(&b[j],&b[i]); } } }}//冒泡排序,带判断标志void bubule(int *b, int n){ int i,j,is_order = 1; for(i=n-1;i>0;i--) { is_order = 1; //每次循环比较之前,设置标志位 for(j=0;j<i;j++) { if(b[j] > b[j+1]) { swap2(&b[j],&b[j+1]); is_order = 0; //一旦有交换,则清除标志位 } } if(is_order) break; }}
6、直接插入排序
【算法思路】:把一个数,插入到一个已经有序的表中。插入时,在有序表中从后往前寻找插入点,顺便移动数据。
//直接插入排序void insert_sort(int *b, int n){ int i,j,tmp; for(i=1; i<n; i++) { tmp = b[i]; for(j=i-1; j>=0 && tmp<b[j]; j--) { b[j+1] = b[j]; } b[j+1] = tmp; }}
7、二路插入排序
【算法思路】:就是在直接插入的基础上,寻找插入点的时候利用二分查找的办法。
//二路插入排序void twoway_insert_sort(int *b, int n){ int i,j,tmp,low,high,mid; for(i=1; i<n; i++) { tmp = b[i]; //二路查找 low = 0; high = i-1; while(low <= high) { mid = (low+high)/2; if(tmp > b[mid]) low = mid+1; else high = mid-1; } for(j=i-1;j>=low;--j) //最终当low > high的时候,我们需要从i~low的位置移动,即插入的位置在low/high+1的位置上。 b[j+1] = b[j]; b[j+1] = tmp; //b[high+1] = tmp; b[low] = tmp;都可以 }}
8、希尔插入排序
【算法思路】:通过一个增量dk,将数组分成S = { k,k+dk,k+2dk,k+3dk…. }(k的范围是0~dk-1),这样子就是将S这个序列进行直接插入排序了。然后随着增量dk的减小,最终减到1,一次直接插入排序排好。
//一趟希尔排序shell sortvoid shellsort(int *b, int n, int dk){ int i,j,k,tmp; for(i=dk; i<n; ++i) //① { if(b[i] < b[i-dk]) { tmp = b[i]; for(j=i-dk; j>=0 && b[j]>tmp; j-=dk) //② { b[j+dk] = b[j]; } b[j+dk] = tmp; } }}void shell_sort(int *b, int n){ int dk[] = {5,3,1}; int i; for(i=0;i<3;++i) { shellsort(b,n,dk[i]); }}
9、二分查找
//二路查找(查找c,返回在b中的位置)int twoway_search(int *b, int n, int c){ int low,high,mid; low = 0; high = n-1; while(low <= high) //重点 { mid = (low + high)/2; if(b[mid] == c) return mid; if(b[mid] < c) low = mid +1; else if(b[mid > c]) high = mid -1; } return -1; //未找到}
10、二叉查找树(排序树、搜索树)->红黑树
左节点值 < 根 < 右节点值
二叉查找树的一般性质:
1.在一棵二叉查找树上,执行查找、插入、删除等操作,的时间复杂度为O(lgn)。
因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn)。
2.但若是一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)。
11、选择排序
选择排序的思路:
①初始状态:无序区为R[1..n],有序区为空。
②第1趟排序
在无序区*R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换*,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
……
③第i趟排序
第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)。
堆排序
数组从1开始计算,0舍弃。
1、调整堆
//a为堆数组,i为需要调整的子树的根节点指针,size为数组中元素的个数void HeapAdjust(int *a,int i,int size) //调整堆 { int lchild=2*i; //i的左孩子节点序号 int rchild=2*i+1; //i的右孩子节点序号 int max=i; //记录根和其左右孩子的最大值指针 if(i<=size/2) //从第一个非叶子节点开始调整 { if(lchild<=size && a[lchild]>a[max]) { max=lchild; } if(rchild<=size && a[rchild]>a[max]) { max=rchild; } if(max!=i) { swap(a[i],a[max]); HeapAdjust(a,max,size); //避免调整之后以max为父节点的子树不是堆 } } }
2、对一个初始的数组建立堆
//a为堆数组,size为数组中元素的个数void BuildHeap(int *a,int size) //建立堆 { int i; for(i = size/2; i >= 1; i--) //从第一个非叶子结点开始建立堆 { HeapAdjust(a,i,size); } }
3、堆排序:建立堆,然后交换堆顶元素和最后一个元素,从新调整堆
void HeapSort(int *a,int size) //堆排序 { int i; BuildHeap(a,size); for(i=size; i>=1; i--) { //cout<<a[1]<<" "; swap(a[1],a[i]); //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面 HeapAdjust(a,1,i-1); //重新调整堆顶节点成为大顶堆 }}
12、二叉树
创建二叉树,按先序遍历创建 12 45 0 65 23 0 0 45 先序创建。
void Create_BiTree(BiTree **t){ int a; BiTree *p; scanf("%d",&a); if(a == 0) *t = NULL; else { p = (BiTree *)malloc(sizeof(BiTree)); if(!p) exit(1); p->data = a; *t = p; //将*t指向p所申请的的内存,此处将改变根节点的指针,因此必须传入根节点的指针的指针。 Create_Tree(&(*t)->left); Create_Tree(&(*t)->right); }}
中序遍历二叉树(非递归算法)
1:
当二叉树不空或者栈不空时,循环
{
当二叉树不空,当前节点进栈, 然后指向左子树
当二叉树空时,出栈一个节点,访问之,然后指向右子树
}
void InOrderTraverse_noRec(BiTree t){ BiTree stack[100],tmp; int top,base; top = base = 0; while(t || top != base) { if(t) //二叉树不空 进栈->往左走一步 { stack[top++] = t; t = t->left; } else //二叉树空 出栈->访问->向右走一步 { t = stack[--top]; printf("%d\n",t->data); t = t->right; } }}
2:
根节点进栈
栈不空时,循环
{
得到栈顶元素,栈顶指针不空,循环,向左走到头,进栈每个节点的左孩子
出栈空节点
然后,栈不空时出栈,访问之,再入栈右孩子指针
}
void InOrderTraverse_noRec2(BiTree t){ BiTree stack[100],tmp; int top,base; top = base = 0; stack[top++] = t; //根节点进栈 while(top != base) //栈不空时,循环 { while(tmp = stack[top-1]) //栈顶元素不空时,进栈所有左孩子节点 { stack[top++] = tmp->left; } top--; //出栈空节点(包括while循环进的最后一个空节点,或下面进栈的一个空的右孩子) if(top != base) { tmp = stack[--top]; printf("%d\n",tmp->data); stack[top++] = tmp->right; } }}
后续遍历二叉树(非递归算法)
void PostOrderTraverse(BiTree t){ if(t) { PostOrderTraverse(t->left); PostOrderTraverse(t->right); printf("%d\n",t->data); }}void PostOrderTraverse_noRec(BiTree t){ BiTree stack[100]; int flag[100],top,base; top = base = 0; while(t || top != base) //二叉树不空或者栈不空 { //压栈直到左子树为空 while(t) { stack[top++] = t; flag[top-1] = 0; t = t->left; } //栈不空并且右子树已经访问过了就该访问根节点了 while(top != base && flag[top-1] == 1) { t = stack[--top]; printf("%d\n",t->data); } //栈不空时取栈顶元素的右孩子 if(top != base) { flag[top-1] = 1; t = stack[top-1]->right; } }}// 后序遍历二叉树的非递归算法void postorder2(Bitree *t){ Bitree *s[32]; // s是指针数组,数组中元素为二叉树节点的指针 int tag[32]; // s中相对位置的元素的tag: 0或1 int top = -1; while (t!=NULL || top != -1) { // 压栈直到左子树为空 while (t != NULL) { s[++top] = t; tag[top] = 0; t = t->lchild; } // 当栈非空,并且栈顶元素tag为1时,出栈并访问 while (top!=-1 && tag[top]==1) { printf("%c ", s[top--]->data); } // 当栈非空时,将栈顶tag置1,并指向栈顶元素的右孩子 if (top != -1) { tag[top] = 1; t = s[top]->rchild; } }}
13、图
使用深度优先搜索求简单路径
求一个顶点v到顶点s的简单路径(没有回路的路径)
例如:求a到e的简单路径
先从a出发,然后访问b,再访问c,此时记住a->b->c的这个路径,
当c又访问a时,此时c已经遍历完了,还没有找到e,因此将c出路径,
再退回到b,b的临节点都访问了,因此将b出路径。
再退回到a,访问未访问的d,将d加入路径,
然后再访问e,加入路径
已经访问到了e,输出路径:a->d->e
算法伪代码:
void DFSsearch(int v, int s, char *path){ visit[v] = TRUE; //访问第v个节点 Append(path, v); //将v节点加入到路径 for(w=FirstAdjVex(v), w!=0 && !found; w=NextAdjVex(v)) //从v的第一个邻节点到最后一个邻节点 { if(w == s) //找到s { found = TRUE; Append(path, w); } else if(!visit[w]) //w未被访问到 { DFSsearch(w,s,path); } } if(!found) { Delete(v,path); //当v这个节点的所有邻节点都访问完了,还未找到,则将v退出路径 }}
使用广度优先搜索求最短路径
例:求顶点3到顶点5的最短路径,应该是3->1->4->
从3开始广度遍历,(广度遍历每次都是增加一个深度,需要一个队列辅助实现)
1)将链队列的节点改为双链,包含一个pre指向深度遍历时的父节点。
2)修改入队列的操作,插入新的队尾结点时,令其pre指向刚刚出队列的结点。
3)修改出队列的操作,出队列时,仅移动队头指针,而不删除。
4)当找到5时,从5开始顺着pre指针到队列头,此即其路径。
- 面试——数据结构
- 面试准备—数据结构
- 【面试准备】数据结构—树
- 面试突击——数据结构基础,排序
- 面试——数据结构与算法
- 面试数据结构篇—单链表常考点汇总
- 数据结构面试之一——单链表常见操作
- 数据结构面试之三——栈的常见操作
- 数据结构面试之四——队列的常见操作
- 数据结构面试之七——图的常见操作
- 数据结构面试之十三——Hash表(散列表)
- 数据结构面试之十四——字符串的模式匹配
- 面试突击(1)——数据结构基础,排序
- 数据结构面试之一——单链表常见操作
- 数据结构面试之三——栈的常见操作
- 数据结构面试之四——队列的常见操作
- 数据结构面试之一——单链表常见操作
- 数据结构面试之三——栈的常见操作
- Android使用腾讯定位SDK显示当前地址
- LeetCode 208: Implement Trie (Prefix Tree)
- 第十六周 程序阅读
- 打车软件烧钱背后的商业逻辑
- android 自定义对话框 位置
- 面试——数据结构
- 《实用OpenCV》(六) 图像中的形状(1)
- Android项目打包成APK文件
- sed基础知识
- MongoDB安装为Windows服务方法与注意事项
- OJ矩阵之和
- 我的.Net技术体系
- SDWebImage 使用
- CSRF模拟post请求