时间空间复杂度
常对幂指阶
O(1)<O(log_2n)<O(n)<O(nlog_2n)<O(n^2)<O(2^n)<O(n!)<O(n^n)$
指针基础概念
&取地址符 *间接运算符
1 2 3
| int number = 1; int *p = &number; printf("%d",*p);
|
malloc函数动态分配的空间需要手动free掉,属于内存中的堆区,系统不会自动回收空间,所以malloc和free的操作是成对出现的。
点操作符.与箭头操作符->的区别
相同点:两个都是二元操作符,其右操作符是成员的名称
不同点:点操作符左边的操作数是一个“结果为结构”的表达式;
箭头操作符左边的操作数是一个指向结构的指针
线性表
线性表的插入删除O(N)
1 2 3 4 5 6 7 8 9 10 11 12 13
| void ListInsert(SqList *L,int i,int e){ for(int j=L->length;j>=i;j--){ L->data[j]=L->data[j-1]; } L->data[i-1]=e; L->length++; } void ListDelete(SqList *L,int i){ for(int j=i;j<L->length;j++){ L->data[j]=L->data[j+1]; } L->length--; }
|
单链表
节点的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct LNode{ int val; struct LNode *next; };
typedef struct LNode Lnode,*LinkList; bool InitList(LinkList L){ L=(LNode*)malloc(sizeof(LNode)); if(L==NULL) return false; L->val=0; L->next=NULL; return true; } bool empty(LinkList L){ if(L->next==NULL) return true; return false; }
|
节点的插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| bool ListInsert(LinkList L,int i,int e){ if(i<1)return false; LNode *p=GetElem(L,i-1); InsertNextNode(p,e); } bool InsertNextNode(LNode *p,int e){ if(p==NULL)return false; LNode *s=(LNode *)malloc(sizeof(LNode)); s->val=e; s->next=p->next; p->next=s; return true; } bool InsertPriorNode(LNode *p,int e){ if(p==NULL)return false; LNode *s=(LNode *)malloc(sizeof(e)); if(s==NULL)return false; s-next=p->next; p->next=s;
s->val=p->val; p->val=e; }
|
单链表的查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| LNode *GetElem(LinkList L,int i){ if(i==0)return L; int j=0; LNode *p=L->next; while(p!=NULL&&j<i){ p=p->next; j++; } if(p==NULL)return NULL; return p; } LNode *LocateElem(LinkList L,int e){ int j=0; LNode *p=L->next; while(p!=NULL&&p->val!=e){ p=p->next; } return p; }
|
链表的头插法尾插法存储
头插法可以实现链表的逆置:
1 2 3 4 5 6 7 8 9
| LinkList List_HeadInsert(LinkList L){ L=(LNode *)malloc(sizeof(LNode)); L->next=NULL; int t; while(scanf("%d")!=EOF){ InsertNextNode(L,t); } }
|
尾插法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| LinkList List_TailInsert(LinkList L){ int t; L=(LNode *)malloc(sizeof(LNode)); LNode *r=L; while(scanf("%d",&t)!=EOF){ LNode *s=(LNode *)malloc(sizeof(LNode)); s->val=t; r->next=s; r=s; } r->next=NULL; return L; }
|
后缀表达式
将中缀表达式转换为后缀表达式
input: 2+3*(7-4)+8/4
output: 2 3 7 4 - * + 8 4 / +
从左到右遍历表达式中每个数字和符号,遇到数字就输出,若是符号则判断它与栈顶符号的优先级,如果是右括号或者优先级不高于(也就是<=)栈顶符号,则栈顶元素依次出栈,然后将当前符号进栈,一直到结束。
1 2 3 4 5 6
| int opaIsBiggerThanopb(char a,char b){ if((a=='*'||a=='/')&&b!='*'&&b!='/'&&b!='('){ return 1; } return 0; }
|
双链表
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef struct DNode{ int val; struct DNode *next,*prior;
}DNode,*DLinkList; bool InitDLinkList(DLinkList L){ L=(DNode *)malloc(sizeof(DNode)); if(L==NULL)return false; L->next=NULL; L->prior=NULL; return true;
}
|
双链表后插,前插操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| bool InsertNextDNode(DNode *p,DNode *s){ if(p==NULL||s==NULL)return false; s->next=p->next; if(p->next!=NULL) p->next->prior=s; s->prior=p; p->next=s; return true; } bool InsertPriorDNode(DNode *p,DNode *s){ if(p==NULL||s==NULL)return false; InsertNextDNode(p->next,s); return true; }
|
双链表后删操作和链表清空操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| bool DeleteNextDNode(DNode *p){ if(p==NULL||p->next==NULL)return false; DNode *s=p->next; if(s->next!=NULL) s->next->prior=p; p->next=s->next; free(s); return true; } void DestoryList(DlinkList L){ while(L->next!=NULL) DeleteNextDNode(L); free(L); L=NULL; }
|
顺序表和链表的优劣
总结
顺序存储:占用空间连续,支持随机存取,查找O(1)改变容量不方便,增删操作O(N)
链式存储:离散小空间分配,增删O(1)不可随机存取,查找O(N),数据密度低
初始化
顺序存储:预分配大片连续空间
静态分配:容量不可改变。系统自动回收空间
动态分配(malloc free):容量可改变,但移动数据时间长。需要手动free
链式存储:只需要分配一个头指针