数据结构总结

每次敲都有新感受
设计模式
线性表:一对一
1 线性表的顺序存储 
void *指针可以指向任意变量的内存空间:


普通的链表:


头文件:
_LinkNode节点结构体LinkNode
各种接口
接口全是void *=LinkList


相应.c文件:具体链表操作
LinkList链表结构体LList
接口的实现:都是对链表结构体的操作
初始化:目的是返回句柄


让C语言支持cpp中的 函数重载:




左孩子右兄弟可以把所有树 变成二叉树 从根节点开始
满二叉树:完美对称
完全二叉树:最后一层 左边... 表达不出来
优点在于:可以用数组表示出来 又可以还原成树 2i 2i+1


非递归遍历:栈 二次压栈 空的也要压进去


先把根压进去 接着循环
栈里 顶是true则输出 NULL则继续弹出 直到弹到一个false 
再把false的 两个根节点压进去 此时的false变为了true 弹出


调用顺序栈API


//树专业定义:有且只有一个称为根的节点
//有若干个互不相交的子树 这些树本身也是一个树 
//通俗点 树是由节点和边组成,每个节点只有一个父节点但可以有多个子节点
//但有一个节点例外 该节点没有父节点 称为根节点 


//叶子节点 没有子节点的节点
//非终端节点 就是非叶子节点
//深度 有几层 从根1开始
//度 子节点的个数
//一般树 任意一个节点的子节点 个数不受限制
//二叉树 任意一个节点 的个数最多两个 且子节点位置不能更改 
//一般二叉树
//满二叉树 都是挂两个 组成一个大三角形 高级对称的那个 
// 完全二叉树  如果删除了满二叉树最低层 最右边的连续若干个节点 这样形成的二叉树就是完全二叉树 
//森林 n个互不相交的树的集合 
/*
一般树的存储
双亲表示法
孩子表示法
双亲孩子表示法
二叉树表示法
二叉树的遍历
先序遍历:先访问根节点 再先序访问左子树再先序访问柚子树
已知两种遍历序列求原始二叉树
*/


二叉树节点定义typedef只有3行话(重要的)而遍历也是3行话(不算外套那个if作为退出条件) 马格丹3行情书啊! 
叶子个数:3行if 外套一个if作为退出条件.
一旦遇到空就全部回溯
深度:
拷贝二叉树:得释放TreeNode* CopyTree(TreeNode* root)
井号法创建二叉树:得释放TreeNode* CreateBinaryTree()










************************************************************
排序
那个排序的标志位 加的很吊
冒泡
int i, j, temp;
for (j = 0; j < n - 1; j++)
for (i = 0; i < n - 1 - j; i++)
{
if(a[i] > a[i + 1])//if(a[i] > =a[i + 1])这样就会变成不稳定的了
{
temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
}
}
冒泡稳定的根本原因是它是两个相邻元素的比较


直接排序:
序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中
2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法



插入排序:
插入排序也是稳定排序,联想冒泡
关键字比较次数永远是n(n-1)/2  记录移动次数最多为3(n-1),最少0次 前者起主导作用,因此实际上时间复杂度还是O(n^2)
void InsertSort(int arr[], int len){
for (int i = 1; i < len; i++){
if (arr[i] <arr[i - 1]){//a[i]  
int temp = arr[i];// 缓存a[i] 把小的缓存下来
int j;
for (j = i - 1; j >= 0 && temp <arr[j]; j--){// 把a[i]插入到左边有序序列的合适位置
arr[j + 1] = arr[j];//右移
}
arr[j + 1] = temp;//一旦a[i]小于了某个数或者到头 插进去
}}}
缓存当前值;符合a[i]还是小的条件 则继续右移;一旦大于等于 则插入


希尔排序shell:在插入排序的基础之上 所以也是稳定的? 错是不稳定的
建议画图 12345678910 有三行那个图
跳着分 对每小组插入排序
减小间隔 同上
最后进行 插入排序

希尔排序的时间复杂度是:O(nlogn)~O(n2),平均时间复杂度大致是O(n√n)
算法的复杂度为n的1.2次幂:
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长
最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,
希尔排序的时间复杂度会比o(n^2)好一些。

int increasement = len;//10个元素
do{
//每次递减增量
increasement = increasement / 3 + 1;     //4 2 1
for (int i = 0; i < increasement; i++)分increasement组
{
for (int j = i + increasement; j < len; j += increasement)//根据增量 分好一组 插入排序
{
if (arr[j] < arr[j - increasement])//a[j]小
{
int temp = arr[j];//缓存a[j]
int k = j - increasement;//与其 前面一个元素比较
for (; k >= i && temp < arr[k]; k -= increasement)//找位置。i也可以写成0
{
arr[k + increasement] = arr[k];//右移
}
arr[k + increasement] = temp;//插入
}
}
}
} while (increasement > 1);


快速排序:
建议画图,1-1024个数(第一个数是511 中间是1) 以511为基准 ......
第一次循环n次 第二次循环2*n/2次 ...... 一共有 log2n 行
所以 平均时间复杂度log2(n)*n
确定一个pivot  排序后 左边的值都小于pivot 右边都大于pivot
再分别对两边 取pivot排序

比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,
下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定
的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。
void QuickSort(int arr[], int start, int end){
int i = start;
int j = end;
int pivot = arr[start];
if (i < j)
{
while (i < j)
{
while (i < j&&arr[j] >= pivot){ j--; }
if (i < j){ arr[i] = arr[j]; i++; }
while (i < j&&arr[i] < pivot){ i++; }
if (i < j){ arr[j] = arr[i]; j--; }
}
arr[i] = pivot;
QuickSort(arr, start, i - 1);
QuickSort(arr, i + 1, end);
}
}
6作为基准值 被挖走
6 3 8 2 7 1 4 9 10 11
i <--j 

4(a[j])填入6(a[i])的位置:
4 3 8 2 7 1 4 9 10 11
i--> j
i    j
---------------------------
8(a[i])填入4(a[j])的位置:
4 3 8 2 7 1 8 9 10 11
i     j   

只要有替换就--或++
??????????????????????????????????????????、
http://blog.csdn.net/hr10707020217/article/details/8985592
 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2
 个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到
 原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小
 相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳
 定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我
 们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。
 所以,归并排序也是稳定的排序算法。




归并排序:
void Merge(int arr[], int start, int end, int mid, int tempSpace[]){
int iStart = start;int iEnd = mid;
int jStart = mid + 1;int jEnd = end;
int length = 0;
while (iStart <= iEnd && jStart <= jEnd){//一旦有一个写完到临时空间,就退出来
if (arr[iStart] < arr[jStart]){//排序插入
tempSpace[length] = arr[iStart];
iStart++;
}
else{
tempSpace[length] = arr[jStart];
jStart++;
}
length++;
}
while (iStart <= iEnd){//有一个已经结束了,把另外一个 还没有结束(<=)的直接插入
tempSpace[length] = arr[iStart];
length++;
iStart++;
}
while (jStart <= jEnd){
tempSpace[length] = arr[jStart];
length++;
jStart++;
}
//将辅助空间中有序序列覆盖原空间数据
for (int i = 0; i < length; i ++){
arr[start + i] = tempSpace[i];
}
}
void MergeSort(int arr[], int start, int end, int tempSpace[])
{
if (start == end){
return;
}
//计算中间下标
int mid = (start + end) / 2;
//拆分左半拉
MergeSort(arr, start, mid, tempSpace);
//拆分右半拉
MergeSort(arr, mid + 1, end, tempSpace);
//合并合并两个有序序列
Merge(arr,start,end,mid,tempSpace);
}
***********************************************
堆排序:不需要额外空间
完全二叉树的结构


我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其
2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程
是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之
间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, ... 1这些个父节点选择元
素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个
父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,
堆排序不是稳定的排序算法。


用428057139
//堆调整:顾名思义,调整爹家族,使得爹的位置是个最大值  内含递归
void HeapAdjust(int arr[],int len, int index){//数组 总长度 爹的位置
int lchild = index * 2 + 1;//都是位置 不要迷
int rchild = index * 2 + 2;
int max = index;//假设爹的位置为最大值的位置
if (index < len){//找出叶子最大值的位置 赋值给max
if (lchild < len && arr[lchild] > arr[max]){
max = lchild;
}
if (rchild < len && arr[rchild] > arr[max]){
max = rchild;
}
if (max != index){
//交换两个元素
MySwap(arr,max,index);//交换使得 爹最大
//递归调整堆
HeapAdjust(arr, len, max); //调整堆 叶子当爹
}
}
}


堆排序:
void HeapSort(int arr[], int len){
//初始化堆
for (int i = len / 2 - 1; i >= 0; i--){//找最后叶子的爹的位置i    i从0开始
HeapAdjust(arr,len,i); 
}
for (int i = len - 1; i > 0; i --){
//交换头尾两个元素
MySwap(arr, 0, i);//交换后 最后一个元素是最大值 然后递归
//从头部开始调整堆
HeapAdjust(arr,i,0);//使各个爹的位置是个最大值
}
}
初始胡堆的作用是 堆顶是最大值9

所有的递归都可以自行定义stack来解递归



二分查找法:时间复杂度是O(log n)
   条件:存储在数组中 有序排列
   数组是递增排列,数组中的元素互不相同
int bsearch(int array[], int low, int high, int target)
{
    if (low > high) return -1;
    
    int mid = (low + high)/2;
    if (array[mid]> target)
        return    binarysearch(array, low, mid -1, target);
    if (array[mid]< target)
        return    binarysearch(array, mid+1, high, target);
    
    //if (midValue == target)
        return mid;
} 此二分的递归是尾递归,它不关心递归前的所有信息。所以它的非递归实现可以不用栈 直接用while
int bsearchWithoutRecursion(int array[], int low, int high, int target)
{
    while(low <= high)
    {
        int mid = (low + high)/2;
        if (array[mid] > target)
            high = mid - 1;
        else if (array[mid] < target)
            low = mid + 1;
        else //find the target
            return mid;
    }
    //the array does not contain the target
    return -1;
}




一个算法中的语句执行 次数 称为语句频度或时间频度。记为T(n)。当n不断变化时,时间频度T(n)也会不断变化
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),
使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作
T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。


T(n)=n2+3n+4与T(n)=4n2+2n+1它们的频度不同,但时间复杂度相同,都为O(n2)。常数是4
若算法中语句执行次数为一个常数,则时间复杂度为O(1)
常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2),
立方阶O(n3),..., k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。 


复杂度与时间效率的关系:
c < log2n < n < n*log2n < n2 < n3 < 2n < 3n < n! (c是一个常量)
|--------------------------|--------------------------|-------------|
          较好                     一般              较差
其中c是一个常量,如果一个算法的复杂度为c 、 log2n 、n 、 n*log2n,那么这个算法
时间效率比较高 ,如果是 2n , 3n ,n!,那么稍微大一些的n就会令这个算法不能动了,居
于中间的几个则差强人意。




时间复杂度:O(log2n)
 while (i<=n)
      i=i*2; 

















































































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值