pancake的排序- 1.3 一摞烙饼的排序 《编程之美》读书笔记03

时间:2022-09-19 11:26:29

问题:

    星期五的晚上,一帮同事在希格玛大厦附近的“硬盘酒吧”多喝了几杯。程序员多喝了几杯之后谈什么呢?自然是算法问题。有个同事说:“我以前在餐馆打工,顾客经常点非常多的烙饼。店里的饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小次序摆好——小的在上面,大的在下面。由于我一只手托着盘子,只好用另一只手,一次抓住最上面的几块饼,把它们上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。我后来想,这实际上是个有趣的排序问题:假设有n块大小不一的烙饼,那最少要翻几次,才能达到最后大小有序的结果呢?”

你能否写出一个程序,对于n块大小不一的烙饼,输出最优化的翻饼过程呢?

基本上,这种问题只有通过生成树搜索、动态规划、贪心算法来解决。显然,不容易找出一种贪心算法,使得通过让子过程最优使得最终输出最优。动态规划的问题是规划的子过程太多了,遍历子过程的比求解还复杂。那么只能用生成树搜索的办法。
书上给出的DFS+剪枝的方法。我觉得WFS应该更快,但是更耗内存。

1、先把书上的解输入eclipse

用书上的例子测试 {3,2,1,6,5,4,9,8,7,0}

4 8 6 8 4 9
Search Times: 172126
Total Swap times: 6

连作者都觉得应该好好优化了。

按照作者的思路,让上限(最少翻转次数上限值)或者下限(估算翻转的需要最少次数)越接近最终解能让剪枝更快接近最终解。这是显而易见的。

因此我们可以采取的策略有:

1、if(step + nEstimate > m_nMaxSwap)  > 改为 >= 这是因为如果在这个解法中,估算的翻转次数已经大于等于了已知的最少翻转次数,哪怕是等于,也没有必要做下去了。

2、书上的初始上限,用的是2n-1这个固定值。而书上也介绍了一种最简单的翻转烙饼归位的算法,那就是每次将未归位的最大的烙饼先翻到最上面,再翻到指定的位置。我们用这种方法很快【算法复杂度O(n)】就可以得出一个已知的翻转方法,这种方法的翻转次数肯定小于等于2n-1。那么就用这种方法来确定初始上限好啦,一般情况下,上限可以被减小很多。

3、在2中,我们减小了上限,接着可以考虑有没有可能在搜索的过程中让nEstimate尽可能的变大。考虑这样一种情况,所有的烙饼都按照大小排位的,但是不是由小到大【0123456789】,是反过来由大到小【9876543210】,在这种情况下也是需要翻转一次才行的。因而在LowerBound函数return ret; 前插入一行:

if (pCakeArray[nCakeCnt-1] != nCakeCnt-1) ret++;

这里我们默认了烙饼是0123456789这样的数顺序排列的,如果不是,可以在之前检查替换。

然后我将原算法改进。

//============================================================================
// Name : Beauty of Code 1.3.CPP
// Author : Original Author
// Version : 1.0
// Copyright :
// Description : Pancake Sorting
//============================================================================ #include <iostream>
using namespace std; class CPrefixSorting
{
private:
int* m_CakeArray;//烙饼信息数组
int m_nCakeCnt;//烙饼个数
int m_nMaxSwap;//最大交换次数,最多 m_nCakeCnt*2 int* m_SwapArray;//交换结果数组
int* m_ReverseCakeArray;//当前反正烙饼信息数组
int* m_ReverseCakeArraySwap;
int m_nSearch;//当前搜索次数信息 public:
//构造函数
CPrefixSorting()
{
m_nCakeCnt=;
m_nMaxSwap=;
}
~CPrefixSorting()
{ if(m_CakeArray != NULL)
delete m_CakeArray;
if(m_SwapArray != NULL)
delete m_SwapArray;
if(m_ReverseCakeArray != NULL)
delete m_ReverseCakeArray;
if(m_ReverseCakeArraySwap !=NULL)
delete m_ReverseCakeArraySwap; } //寻找当前翻转的上界
int UpperBound(int nCakeCnt)
{
return nCakeCnt*-;
}
//根据当前数组信息,判断翻转的最小下界,
int LowerBound(int * pCakeArray,int nCakeCnt)
{
int t,ret=;
for(int i=;i<nCakeCnt;i++)
{
t=pCakeArray[i]-pCakeArray[i-];
if((t==)||(t==-))
{
}
else
{
ret++;
}
} if (pCakeArray[nCakeCnt-] != nCakeCnt-) ret++;
return ret; } //判断是否排序好
bool IsSorted(int * pCakeArray, int nCakeCnt)
{
for(int i=;i<nCakeCnt;i++)
{
if(pCakeArray[i-]>pCakeArray[i])
return false;
}
return true;
} void Reverse(int nBegin, int nEnd)
{
assert(nEnd>nBegin); int i,j,t;
//nBegin到nEND的数颠倒
for(i=nBegin,j=nEnd;i<j;i++,j--)
{
t=m_ReverseCakeArray[i];
m_ReverseCakeArray[i]=m_ReverseCakeArray[j];
m_ReverseCakeArray[j]=t;
} } void Search (int step)
{
int i,nEstimate;
m_nSearch++; nEstimate=LowerBound(m_ReverseCakeArray,m_nCakeCnt);
if(step+nEstimate>=m_nMaxSwap)
return;
if(IsSorted(m_ReverseCakeArray,m_nCakeCnt))
{
if(step<m_nMaxSwap)
{
m_nMaxSwap =step;
for(i=;i<m_nMaxSwap;i++)
m_SwapArray[i]=m_ReverseCakeArraySwap[i];
}
return; }
for(i=;i<m_nCakeCnt;i++)
{
Reverse(,i);
m_ReverseCakeArraySwap[step]=i;
Search(step+);
Reverse(,i);
}
} inline void mrev(int* pCakeArray,int nEnd)
{
int i,j,t;
//nBegin到nEND的数颠倒
for(i=,j=nEnd;i<j;i++,j--)
{
t=pCakeArray[i];
pCakeArray[i]=pCakeArray[j];
pCakeArray[j]=t;
} } int MaxSort(int* pCakeArray, int nCakeCnt)
{
int *cake=new int[nCakeCnt];
for(int i=;i<nCakeCnt;i++)
cake[i]=pCakeArray[i]; int SwapTimes;
int n_Swap;
int biggest;
n_Swap=nCakeCnt;
biggest=;
SwapTimes=;
//cout<<"go"<<endl;
while(n_Swap>&&
!IsSorted(cake,nCakeCnt)
)
{ for (int i=;i<n_Swap;i++)
{
if(cake[i]>cake[biggest])
biggest=i;
} if(biggest+==n_Swap)
{
//cout<<".1."<<endl;
}
else if(biggest==)
{ // cout<<".2."<<endl;
mrev(cake,n_Swap-);
SwapTimes++;
/*
cout<<SwapTimes<<": "<<"big"<<biggest+1<<";";
for(int i=0;i<nCakeCnt;i++)
cout<<cake[i]<<",";
cout<<endl;
*/
}
else
{ //cout<<".3."<<endl;
// cout<<"n_Swap"<<n_Swap<<endl;
// cout<<""
mrev(cake,biggest);
SwapTimes++;
/*
cout<<SwapTimes<<": "<<"big"<<biggest+1<<";";
for(int i=0;i<nCakeCnt;i++)
cout<<cake[i]<<",";
cout<<endl;
*/
mrev(cake,n_Swap-);
SwapTimes++;
/*
cout<<SwapTimes<<": "<<"big"<<biggest+1<<";";
for(int i=0;i<nCakeCnt;i++)
cout<<cake[i]<<",";
cout<<endl;
*/
//biggest=0;
}
biggest=;
n_Swap--; } delete cake;
cout<<"The Upper Solve: "<<SwapTimes<<endl;
return SwapTimes;
} void Init(int* pCakeArray, int nCakeCnt)
{
assert(pCakeArray!=NULL);
assert(nCakeCnt > );
m_nCakeCnt =nCakeCnt;
//初始化烙饼数组
m_CakeArray = new int[m_nCakeCnt]; assert(m_CakeArray != NULL); for(int i=; i<m_nCakeCnt; i++)
{
m_CakeArray[i]=pCakeArray[i];
} //设置最多交换次数
m_nMaxSwap =MaxSort(pCakeArray, nCakeCnt);
//初始化交换结果数组
m_SwapArray=new int[m_nMaxSwap+];
assert(m_SwapArray !=NULL);
//初始化中间交换结果
m_ReverseCakeArray = new int[m_nCakeCnt]; for(int i=;i<m_nCakeCnt;i++)
{
m_ReverseCakeArray[i]=m_CakeArray[i];
} m_ReverseCakeArraySwap =new int[m_nMaxSwap]; } void Run(int* pCakeArray,int nCakeCnt)
{
Init(pCakeArray,nCakeCnt);
m_nSearch=;
Search(); }
void Output()
{
for (int i=;i<m_nMaxSwap;i++)
cout<<m_SwapArray[i]<<" ";
cout<<endl; cout<<"Search Times: "<<m_nSearch<<endl;
cout<<"Total Swap times: "<<m_nMaxSwap<<endl;
} }; int main() { int a[]={,,,,,,,,,};
int n=;
CPrefixSorting my;
//my.Init(a,n);
cout<<"go"<<endl;
//my.MaxSort(a,n);
my.Run(a,n);
my.Output(); return ;
}
go
The First Solve: 10
4 8 6 8 4 9
Search Times: 1045
Total Swap times: 6

  嗯,算法效率提高了挺多的。

4、(1)如果排序排到  3,2,1,6,5,4,0,7,8,9的时候,显然最后面三个饼已经就绪了,就没有必要搜索了。那么可以改进search()函数,将已经就绪m的烙饼剔除,排序前面n-m个烙饼。

(2)还有,如果这次翻转的是第k个烙饼,那么下次就没有必要再翻转k个烙饼,因为这样只会回到上次的情况,完全没有改善。

(3)如果在step=i次翻转后,递归的某个子函数找到了解,那么就没有必要再在这个step的状态里递归搜索其他的解了。因为即使找到了,也不可能比这个解更优。

嗯,我们在问题本身的优化已经很很多了。现在对DFS算法进行优化。

搜索算法一般可以用 爬山法、best-first法、A*算法来优化。简单的说就是向前预测一步或者更多。

5、如果我们可以迅速找到一个比较优的解法,那么搜索的次数就可以大大被减少。这显然有点难。

我们可以退而求其次,通过预测下一次翻转可能的下限来优化搜索的路径,迅速找到最优解。

在原程序中,我们可以通过 step=i 状态 的nEstimate估算,来确定 step=i 状态的解的下限。

对于step=i 状态向下进行step = i+1 状态的递归搜索时,我们原来的做法是从第2个烙饼 到n-1个依次去试,显然这不是一个最聪明的方法。

我们考虑估算step =i 时nEstimate =LowBoundry({3,2,1,6,5,4,9,8,7,0})

而step = i+1 时 nEstimate=LowBoundry({3,2,1,6,5,4,9,8,7,0},翻转第k个) 非要通过翻转烙饼以后才能估算出来么?显然不用。

那么我们就可以计算出 k=1到n-2时的下一步的nEstimate,按照从小到大的顺序排序,估算小的先搜索,如果step+nEstimate大于目前的最优解则不用搜索了。

这样就又一次大幅度优化了算法。

#include <iostream>
using namespace std; class CPrefixSorting
{
private:
int* m_CakeArray;//烙饼信息数组
int m_nCakeCnt;//烙饼个数
int m_nMaxSwap;//最大交换次数,最多 m_nCakeCnt*2
int* m_SwapArray;//交换结果数组
int* m_ReverseCakeArray;//当前反正烙饼信息数组
int* m_ReverseCakeArraySwap;
int m_nSearch;//当前搜索次数信息
int reversecount;
int lastswap;
//int* EstimateArray;//判断搜索情况
public:
//构造函数
CPrefixSorting()
{
m_nCakeCnt=;
m_nMaxSwap=;
reversecount=;
}
~CPrefixSorting()
{ if(m_CakeArray != NULL)
delete m_CakeArray;
if(m_SwapArray != NULL)
delete m_SwapArray;
if(m_ReverseCakeArray != NULL)
delete m_ReverseCakeArray;
if(m_ReverseCakeArraySwap !=NULL)
delete m_ReverseCakeArraySwap;
//if(EstimateArray !=NULL)
// delete EstimateArray;
} //寻找当前翻转的上界,由最简单的解法取代
int UpperBound(int nCakeCnt)
{
return nCakeCnt*-;
}
//根据当前数组信息,判断翻转的最小下界,
int LowerBound(int * pCakeArray,int nCakeCnt)
{
int t,ret=;
for(int i=;i<nCakeCnt;i++)
{t=pCakeArray[i]-pCakeArray[i-];
if((t==)||(t==-))//如果左右相邻,则不增加
{}
else
{ret++;}
}
if (pCakeArray[nCakeCnt-] != nCakeCnt-) ret++;//如果最后一个不是最大的,上界肯定要+1
return ret;
} //判断是否排序好
bool IsSorted(int * pCakeArray, int nCakeCnt)
{
for(int i=;i<nCakeCnt;i++)
{
if(pCakeArray[i-]>pCakeArray[i])
return false;
}
return true;
} void Reverse(int nBegin, int nEnd)
{
reversecount++;
assert(nEnd>nBegin);
int i,j,t;
//nBegin到nEND的数颠倒
for(i=nBegin,j=nEnd;i<j;i++,j--)
{
t=m_ReverseCakeArray[i];
m_ReverseCakeArray[i]=m_ReverseCakeArray[j];
m_ReverseCakeArray[j]=t;
}
} void Search (int step)
{
m_nSearch++;
int i,nEstimate;
nEstimate=LowerBound(m_ReverseCakeArray,m_nCakeCnt);
if(step+nEstimate>=m_nMaxSwap)
return; if(IsSorted(m_ReverseCakeArray,m_nCakeCnt))
{
if(step<m_nMaxSwap)
{
m_nMaxSwap =step;
for(i=;i<m_nMaxSwap;i++)
m_SwapArray[i]=m_ReverseCakeArraySwap[i];
}
return; }
for(i=;i<m_nCakeCnt;i++)
{
Reverse(,i);
m_ReverseCakeArraySwap[step]=i;
Search(step+);
Reverse(,i);
}
}
bool Search_V1 (int step,int CakeCnt)
{
int i,nEstimate;
int* EstimateArrayValue;
int* EstimateArrayID; EstimateArrayValue= new int[CakeCnt+];
EstimateArrayID = new int[CakeCnt+];
m_nSearch++;
nEstimate=LowerBound(m_ReverseCakeArray,m_nCakeCnt);
if(step+nEstimate>=m_nMaxSwap)
return false;
CakeCnt=returnorder(m_ReverseCakeArray,CakeCnt); if(CakeCnt==)//==
{ if(step<m_nMaxSwap)
{ m_nMaxSwap =step;
for(i=;i<m_nMaxSwap;i++)
m_SwapArray[i]=m_ReverseCakeArraySwap[i];
}
return true;
} for(i=;i<CakeCnt;i++)
{
EstimateArrayValue[i]=returnEstimate(m_ReverseCakeArray,i,CakeCnt);
EstimateArrayID[i]=i; }
int temp; for (i=;i<CakeCnt;i++)
{ for(int k=i+;k<CakeCnt;k++)
{ //插入排序,将估算值由小到大排序
if(EstimateArrayValue[i]>=EstimateArrayValue[k])
{
temp=EstimateArrayValue[k];
EstimateArrayValue[k]=EstimateArrayValue[i];
EstimateArrayValue[i]=temp; temp=EstimateArrayID[k];
EstimateArrayID[k]=EstimateArrayID[i];
EstimateArrayID[i]=temp;
}
}
} if(step!=-)//debug
{
cout<<step<<"~~ E:"<<nEstimate<<endl;
for(int kk=;kk<m_nCakeCnt;kk++)
cout<<m_ReverseCakeArray[kk]<<" ";
cout<<endl;
/* for(int kk=1;kk<CakeCnt;kk++)
cout<<EstimateArrayID[kk]<<"p ";
cout<<endl;
for(int kk=1;kk<CakeCnt;kk++)
cout<<EstimateArrayValue[kk]<<"v ";
cout<<endl;*/
} for(i=;i<CakeCnt;i++)
{
if(EstimateArrayValue[i]+step >= m_nMaxSwap)
break;
if(lastswap==EstimateArrayID[i])
continue;
lastswap=EstimateArrayID[i];
Reverse(, EstimateArrayID[i]);
m_ReverseCakeArraySwap[step]=EstimateArrayID[i];
bool temp=Search_V1(step+,CakeCnt);
Reverse(, EstimateArrayID[i]);
if (temp==true)
break;
}
delete EstimateArrayValue;
delete EstimateArrayID; }
int returnorder(const int CakeArray[],int count)//去除末尾已就位的烙饼,修正烙饼数组大小。
{
while(count> && count- == CakeArray[count-]) count--;
return count;
} int returnEstimate(const int CakeArray[],int i,int count)//估算在i位置翻转的LowerBound
{
int t,ret=;
for(int k=;k<=i;k++)//计算翻转前段的估算
{
t=CakeArray[k]-CakeArray[k-];
if((t==)||(t==-))//如果左相邻,则不增加
{}
else
{ret++;}
}
for(int k=i+;k<count;k++)//计算翻转后端段的估算
{
t=CakeArray[k]-CakeArray[k-];
if((t==)||(t==-))//如果左相邻,则不增加
{}
else
{ret++;}
} //t=CakeArray[0]-CakeArray[i-1];
//if (!((t==1)||(t==-1))) ret++;
t=CakeArray[i+]-CakeArray[];
if (!((t==)||(t==-))) ret++; if (CakeArray[count-] != count-) ret++;//如果最后一个不是最大的,上界肯定要+1 // if (count==10)
// cout<<ret<<" "; return ret; } void mrev(int* pCakeArray,int nEnd)
{
int i,j,t;
//nBegin到nEND的数颠倒
for(i=,j=nEnd;i<j;i++,j--)
{
t=pCakeArray[i];
pCakeArray[i]=pCakeArray[j];
pCakeArray[j]=t;
} } int MaxSort(int* pCakeArray, int nCakeCnt)
{
int *cake=new int[nCakeCnt];
for(int i=;i<nCakeCnt;i++)
cake[i]=pCakeArray[i];
int SwapTimes;
int n_Swap;
int biggest;
n_Swap=nCakeCnt;
biggest=;
SwapTimes=;
n_Swap=returnorder(cake,nCakeCnt);
while(n_Swap>)
{ for (int i=;i<n_Swap;i++)//找到最大的
{
if(cake[i]>cake[biggest])
biggest=i;
} if(biggest+==n_Swap)
{
//cout<<".1."<<endl;
}
else if(biggest==)
{ // cout<<".2."<<endl;
mrev(cake,n_Swap-);
SwapTimes++;
}
else
{ mrev(cake,biggest);
SwapTimes++;
mrev(cake,n_Swap-);
SwapTimes++;
}
biggest=;
n_Swap=returnorder(cake,n_Swap);
} delete cake;
cout<<"The First Solve: "<<SwapTimes<<endl;
return SwapTimes;
} void Init(int* pCakeArray, int nCakeCnt)
{
assert(pCakeArray!=NULL);
assert(nCakeCnt > );
m_nCakeCnt =nCakeCnt;
//初始化烙饼数组
m_CakeArray = new int[m_nCakeCnt]; assert(m_CakeArray != NULL); for(int i=; i<m_nCakeCnt; i++)
{
m_CakeArray[i]=pCakeArray[i];
} //设置最多交换次数
m_nMaxSwap =MaxSort(pCakeArray, nCakeCnt);
//初始化交换结果数组
m_SwapArray=new int[m_nMaxSwap+];
assert(m_SwapArray !=NULL);
//初始化中间交换结果
m_ReverseCakeArray = new int[m_nCakeCnt]; for(int i=;i<m_nCakeCnt;i++)
{
m_ReverseCakeArray[i]=m_CakeArray[i];
} m_ReverseCakeArraySwap =new int[m_nMaxSwap]; } void Run(int* pCakeArray,int nCakeCnt)
{
Init(pCakeArray,nCakeCnt);
m_nSearch=;
//Search(0);
Search_V1(,nCakeCnt); }
void Output()
{
for (int i=;i<m_nMaxSwap;i++)
cout<<m_SwapArray[i]<<" ";
cout<<endl; cout<<"Search Times: "<<m_nSearch<<endl;
cout<<"Swap times: "<<m_nMaxSwap<<endl;
cout<<"reversecount "<<reversecount<<endl;
} }; int main() { int a[]={,,,,,,,,,};
//int a[]={3,2,1,6,5,4,0,7,8,9};
int n=;
CPrefixSorting my;
//my.Init(a,n);
cout<<"go"<<endl;
//my.MaxSort(a,n);
my.Run(a,n);
my.Output(); return ;
}
go
The First Solve: 10
5 8 5 8 5 9
Search Times: 144
Swap times: 6
reversecount 286

当我觉得我算法已经很优化的时候,我看了http://www.cppblog.com/flyinghearts/archive/2010/06/23/118595.html的文章,查找次数11次。

网上大神还是多。

对于pancake sorting问题为什么是微软的面试题,我觉得很大程度上还是因为比尔盖茨伪辍学者真学神在大学时的某次课程设计,竟然被他们老师改吧改吧发了SCI。http://en.wikipedia.org/wiki/Pancake_sorting#References

pancake sorting的一些变种问题,比如一面黄一面白的烙饼排序,还有查找烙饼数的问题,有时间我来看看。

pancake的排序- 1.3 一摞烙饼的排序 《编程之美》读书笔记03的更多相关文章

  1. 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before

    第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...

  2. 编程之美—烙饼排序问题(JAVA)

    一.问题描述 星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯.程序员多喝了几杯之后谈什么呢?自然是算法问题.有个同事说:"我以前在餐      馆打工,顾 ...

  3. SqlSever基础 order by之后再orderby&comma;双重排序&comma;对排序好的数据中再次进行排序

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...

  4. 七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)

    写在前面: 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的随意序列,又一次排列成一个按keyword有序的序列.因此排序掌握各种排序算法很重要. 对以下介绍的各个排序,我们假定全部排 ...

  5. 《Algorithms Unlocked》读书笔记2——二分查找和排序算法

    <Algorithms Unlocked>是 <算法导论>的合著者之一 Thomas H. Cormen 写的一本算法基础,算是啃CLRS前的开胃菜和辅助教材.如果CLRS的厚 ...

  6. 《algorithms Unlocked》读书笔记3——计数排序

    <Algorithms Unlocked>是 <算法导论>的合著者之一 Thomas H. Cormen 写的一本算法基础,算是啃CLRS前的开胃菜和辅助教材.如果CLRS的厚 ...

  7. java排序算法(十):桶式排序

    java排序算法(十):桶式排序 桶式排序不再是一种基于比较的排序方法,它是一种比较巧妙的排序方式,但这种排序方式需要待排序的序列满足以下两个特征: 待排序列所有的值处于一个可枚举的范围之类: 待排序 ...

  8. java排序算法(八):希尔排序(shell排序)

    java排序算法(八):希尔排序(shell排序) 希尔排序(缩小增量法)属于插入类排序,由shell提出,希尔排序对直接插入排序进行了简单的改进,它通过加大插入排序中元素之间的间隔,并在这些有间隔的 ...

  9. &lt&semi;算法图解&gt&semi;读书笔记&colon;第2章 选择排序

    第2章 选择排序 2.1 内存的工作原理 需要将数据存储到内存时,请求计算机提供存储空间,计算机会给一个存储地址.需要存储多项数据时,有两种基本方式-数组和链表 2.2 数组和链表 2.2.1 链表 ...

随机推荐

  1. 在Jenkins中使用Git Plugin访问Https代码库失败的问题

    最近需要在Jenkins上配置一个Job,SCM源是http://git.opendaylight.org/gerrit/p/integration.git 于是使用Jenkins的Git Plugi ...

  2. JavaScript之JS的执行环境和作用域

    一.执行环境是JavaScript中最为重要的一个概念.执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为,每个执行环境都有一个与之关联的变量对象(variable object),环境 ...

  3. springboot自定义配置信息读取

    在properties配置文件加入自定义配置例如: zxgl.detail.url=http://*****/zxgl-web/news/viewNewsIndexDetail.do?id= #资讯t ...

  4. Chapter 4 Invitations——12

    "I don't know what you mean," I said, my voice guarded. “我不知道你什么意思”我声音谨慎地说道. "It's be ...

  5. SpringBoot系列&colon; 极简Demo程序和Tomcat war包部署

    =================================SpringBoot 标准项目创建步骤================================= 使用 Spring IDE( ...

  6. 使用sass切图

    明日复明日,明日何其多!我生待明日,万事成蹉跎!ruby挺可爱的.没有进入工程项目中使用sass. 尝试是一件快乐的事情.在页面中直接引进编译出来的css即可.

  7. java笔记--BigDecimal的使用

    BigDecimal的运用 --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3877231.html "谢谢-- BigDec ...

  8. 探寻ASP&period;NET MVC鲜为人知的奥秘(1):对LESS的支持

    在ASP.NET MVC3中(从那时开始),我们拥有了对js和css等文件的捆绑(Bundling)和压缩(Minification)的能力,这是ASP.NET性能优化工作的一部分. 想一下很久以前, ...

  9. ajax——优化0126(增删改查:添加查看详情,返回结果类型为JSON型,在窗口显示)

    效果: 鼠标点击查看详情时 数据库: 0126.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...

  10. mysql 行转列,对列的分组求和,对行求和

    CREATE TABLE students( id INT PRIMARY KEY, NAME VARCHAR(11)); CREATE TABLE courses( id INT PRIMARY K ...