408数据结构答题规范
原视频
视频参考,以下为视频的笔记
- 需要写的部分
如果题目要求了函数名、参数列表、返回值类型就按题目的来
- 函数的类型可以是返回值类型或者void类型,如果函数名不清楚里面的功能是什么,在函数title下面最好写一行注释说明函数的功能
输入和输出
print函数+注释
结构体和类
struct node{
int value;
node *next;
node(int value, node *next):value(value),next(next){}
};
动态内存分配
参考链接
C程序使用malloc、realloc等内存申请函数在堆上分配和释放内存的。
用malloc和realloc函数申请的空间不能被系统自动释放,因此,即使这些空间在程序中已经不用,别的程序也不能用。所以C中提供了一个专门的函数free(地址)来释放这些空间。例如free(ptr);,就释放了ptr指向的空间。为防止出现野指针,最好再加上ptr=NULL。
如果发现用malloc申请的空间不够使用或者多了,可以利用realloc函数来调整空间大小。realloc的函数原型为:
void *realloc(void *ptr, size_t size);
第一个参数ptr是重新申请空间的首地址,size是字节数。即从ptr这个地址开始重新申请大小为size字节的空间。用realloc重新申请的空间并不损害原来空间上已存放的数据。
这个函数一般用于调整malloc申请的空间大小,所以ptr一般就设定为malloc函数返回的地址,然后,size设定为更大或更小的数。例如,原来已经使用malloc申请了能存k个int型数据的空间,由于数据量的增加,这个空间不够,可以用p=(int *)realloc(p,sizeof(int)*(k+n));
对空间进行扩展,此时,空间就可以存放k+n个int型数据,值得注意的是realloc得到的内存空间可能不是malloc原申请的空间(原来空间的值可能被释放了),因此,“p=”不能丢。
有了realloc函数,就可以不用先确定非常大的空间,而是先申请少量的空间,等空间不够时,再用realloc来增加。
#include <stdio.h>
int main() {
int *p;
p=(int *)malloc(sizeof(int)*10);
printf("Previous address=%ld\n",p);
for(int i=0;i<=9;i++)
p[i]=i+1;
p=(int *)realloc(p,sizeof(int)*(10+10));//重新分配空间,p=不能丢,realloc得到的空间起始地址可能不是原先申请的空间起始地址
printf("Current address=%ld\n",p);
for(int j=9;j<=19;j++)
p[j]=j+1;
free(p); //释放空间
p=NULL;//防止野指针
return 0;
}
- 申请辅助数组
- 申请链表
① 申请辅助数组
int n=10;
int *a = (int *)malloc(n*sizeof(int))//数组
int *b = (int *)malloc(sizeof(int))//单个
free(a);
free(b);
int n = 10;
int *a = new int[n];
int *b = new int;
delete []a;
delete b;
②链表动态申请内存
//创建动态链表
#include<stdio.h>
#include<stdlib.h>
typedef struct student{
int num;//数据域
float score;
struct student *next;//地址域
}Student;
struct student *head = 0,*p1,*p2;
int tempnum,tempnum1;
float tempscore,tempscore1;
tempnum = 202022020;
tempscore = 98.5;
//第一个节点
head = p1=p2 = (struct student*) malloc(sizeof(struct student));
if(NULL==p1)return 0;
p1->num = tempnum;
p1->score = tempscore;
p1->next=0;
free(p1);//释放p1指向的空间
p1=NULL;
return 0;
head = p1=p2 = (struct student*) malloc(sizeof(struct student));
C语言中,直到C99标准出现之前,声明数组时在方括号内只能使用整数常量表达式。而C99做了很大改进,允许数组的中的值是整形变量或是整形表达式,这就解释了下面的情况:
int n;
scanf ("%d". &n);
int array[n];
虽然n确实是需要运行时动态确定的变量,但是在C99中,以这种变量作为数组大小的形式已经是允许的了。这样的数组就被称之为“变长数组"。
注意:变长数组是指用整型变量或表达式声明或定义的数组,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是周定的。
可变长数组(变量开数组)
可以直接使用的函数
min函数 返回a、b中的较小值
max函数 返回a、b中的较大值
swap函数 交换a、b的值
abs函数 返回a的绝对值
可以使用的c++库函数
可以使用c++的stack和queue
关于vector和array存在争议,不推荐使用
不可以使用list,priority,queue,map,set等等这些
不可以使用sort和qsort等排序相关的函数,排序算法要求自己实现。
memset函数作用
void *memset(void *s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 [1]
-
第一:memset函数按字节对内存块进行初始化,所以不能用它将int数组初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。
-
memset(void *s, int ch,size_t n);
中ch实际范围应该在0~~255
,因为该函数只能取ch的后八位赋值给你所输入的范围的每个字节,比如int a[5]赋值memset(a,-1,sizeof(int )*5)
与memset(a,511,sizeof(int )*5)
所赋值的结果是一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论ch多大只有后八位二进制有效,而后八位二进制的范围在(0~255)中改。而对字符数组操作时则取后八位赋值给字符数组,其八位值作为ASCII码。 -
第三: 搞反了 ch 和 n 的位置.
一定要记住如果要把一个char a[20]清零,一定是memset(a,0,20*sizeof(char));
而不是memset(a,20*sizeof(char),0);
-
第四: 过度使用memset.
char buffer[4];
memset(buffer,0,sizeof(char)*4);
strcpy(buffer,"123");
//"123"中最后隐藏的'\0'占一位,总长4位。
以下情况并不多余,因某些编译器分配空间时,内存中默认值并不为0:
char buffer[20];
memset(buffer,0,sizeof(char)*20);
memcpy(buffer,"123",3);
//这一条的memset并不多余,memcpy并没把buffer全部覆盖,如果没有memset,
//用printf打印buffer会有乱码甚至会出现段错误。
//如果此处是strcpy(buffer,"123");便不用memset,
//strcpy虽然不会覆盖buffer但是会拷贝字符串结束符
2015 41
用单链表保存m个整数,结点的结构为: ,且|data|<n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若给定的单链表head如下:
则删除节点后的head为:
要点:
(1)给出算法的基本设计思想。
(2)使用C或C++语言,给出单链表结点的数据类型定义。
(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(4)说明你所设计算法的时间复杂度和空间复杂度。
(1)算法的基本思想:
算法的核心思想是用空间换时间。使用辅助数组记录链表中已出现的数值,从而只需对链表进行一趟扫描。
因为|data|≤n,故辅助数组temp的大小至少为n+1,各元素的初值均0。
依次扫描链表中的各结点,同时检查temp[|data|]的值,如果为0,则保留该结点,并令++temp[|data|];否则,将该结点从链表中删除。
(2)使用C语言描述的单链表结点的数据类型定义
# 链表结点定义
#include<stdio.h>
#include<limits.h>
#include<stdlib.h>
typedef struct ListNode{
int data;
struct Node *pNext;
}NODE,*PNODE;
## 筛选链表中绝对值重复的元素
void FiltrateRep(PNODE L,int len)
{
int temp[len];
memset(temp,0,sizeof(int)*len);
PNODE pre,p;
pre=L;
while(pre->pNext!=NULL){
p=pre->pNext;
if(p!=NULL){
if(temp[abs(p->data)]<1){
++temp[abs(p->data)];
pre = p;
}else{
pre->pNext = p->pNext;
free(p);
}
}
}
}
本算法的时间空间复杂度都为O(n)
删除排序链表的重复元素|
我们从指针 cur 指向链表的头节点,随后开始对链表进行遍历。如果当前 cur 与 cur.next 对应的元素相同,那么我们就将 cur.next 从链表中移除;否则说明链表中已经不存在其它与 cur 对应的元素相同的节点,因此可以将 cur 指向 cur.next。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* cur = head;
while (cur->next) {
if (cur->val == cur->next->val) {
cur->next = cur->next->next;
}
else {
cur = cur->next;
}
}
return head;
}
};
删除排序链表的重复元素||
具体地,我们从指针 cur 指向链表的哑节点,随后开始对链表进行遍历。如果当前 cur.next 与 cur.next.next 对应的元素相同,那么我们就需要将 cur.next 以及所有后面拥有相同元素值的链表节点全部删除。我们记下这个元素值 x,随后不断将 cur.next 从链表中移除,直到 cur.next 为空节点或者其元素值不等于 x 为止。此时,我们将链表中所有元素值为 x 的节点全部删除。
如果当前 cur.next 与 cur.next.next 对应的元素不相同,那么说明链表中只有一个元素值为 cur.next 的节点,那么我们就可以将 cur 指向 cur.next。
当遍历完整个链表之后,我们返回链表的的哑节点的下一个节点 dummy.next 即可。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* dummy = new ListNode(0, head);
ListNode* cur = dummy;
while (cur->next && cur->next->next) {
if (cur->next->val == cur->next->next->val) {
int x = cur->next->val;
while (cur->next && cur->next->val == x) {
cur->next = cur->next->next;
}
}
else {
cur = cur->next;
}
}
return dummy->next;
}
};
复杂链表的复制
给定链表的头节点 head ,复制普通链表很简单,只需遍历链表,每轮建立新节点 + 构建前驱节点 pre 和当前节点 node 的引用指向即可。
本题链表的节点新增了 random 指针,指向链表中的 任意节点 或者 nullnull 。这个 random 指针意味着在复制过程中,除了构建前驱节点和当前节点的引用指向 pre.next ,还要构建前驱节点和其随机节点的引用指向 pre.random 。 本题难点: 在复制链表的过程中构建新链表各节点的 random 引用指向。
哈希表
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
Node* copyRandomList(Node* head){
//判断链表是否为空
if(head==NULL)return NULL;
//建立map容器
Node *cur = head;
unordered_map<Node*,Node*> map;
//复制各节点,并建立“原节点->新节点”的Map映射
while(cur!=NULL){
map[cur] = new Node(cur->val);
cur = cur ->next;
}
cur=head;
//构建新链表的next和random指向
while(cur!=NULL){
map[cur]->next = map[cur->next];
map[cur]->random = map[cur->random];
cur = cur->next;
}
//返回新链表的头节点
return map[head];
}