Denne artikkelen vil dykke inn i prinsippene for algoritmdesign. Hvis du ikke har en anelse om hva jeg refererer til, les videre!
Når du hører ordet "algoritme", reagerer du sannsynligvis på en av tre måter:
Hvis du er en av de andre to, er denne artikkelen for deg.
Algoritmer er ikke en spesiell type operasjon, nødvendigvis. De er konseptuelle, et sett med trinn som du tar i kode for å nå et bestemt mål.
Algoritmer er ofte definert i enkle ord som "instruksjoner for å fullføre en oppgave". De har også blitt kalt "oppskrifter". I Det sosiale nettverket, En algoritme er hva Zuckerberg trengte for å gjøre Facemash-arbeidet. Hvis du så filmen, husker du sannsynligvis hva som så ut som en skribentlig likning på et vindu i Marks sovesal. Men hva har den skribente algebra å gjøre med Marks enkle "hot or not" -side?
Algoritmer er faktisk instruksjoner. Kanskje en mer nøyaktig beskrivelse ville være at algoritmer er mønstre for å fullføre en oppgave på en effektiv måte. Zuckerbergs Facemash var et avstemningssted for å avgjøre andres attraktivitet i forhold til en hel gruppe mennesker, men brukeren ville bare få alternativer mellom to personer. Mark Zuckerberg trengte en algoritme som bestemte hvilke personer som skulle matche opp til hverandre, og hvordan å verdsette en stemme i forhold til den persons tidligere historie og tidligere kandidater. Dette krevde mer intuisjon enn å bare telle stemmer for hver person.
For eksempel, la oss si at du ønsket å opprette en algoritme for å legge til 1 til et hvilket som helst negativt tall, og trekke 1 fra et hvilket som helst positivt tall og gjøre ingenting til 0. Du kan gjøre noe slikt (i JavaScript-esque pseudokode):
funksjon addOrSubtractOne (tall) if (number < 0) return number + 1 else if (number < 0) return number - 1 else if (number == 0) return 0;
Du kan kanskje si til deg selv, "Det er en funksjon." Og du har rett. Algoritmer er ikke en spesiell type operasjon, nødvendigvis. De er konseptuelle - et sett med trinn som du tar i kode for å nå et bestemt mål.
Så hvorfor er de en stor avtale? Å legge til eller subtrahere 1 til et nummer er tydeligvis en ganske enkel ting å gjøre.
Men la oss snakke om et sekund om å søke. For å søke etter et tall i en rekke tall, hvordan ville du tenke å gjøre det? En naiv tilnærming ville være å gjenta nummeret, sjekke hvert tall mot det du søker etter. Men dette er ikke en effektiv løsning, og har et meget bredt spekter av mulige sluttider, noe som gjør det til en uregelmessig og upålitelig søkemetode når den skaleres til store søkesett.
funksjon naivSøk (nål, høstack) for (var i = 0; i < haystack.length; i++) if (haystack[i] == needle) return needle; return false;
Heldigvis kan vi gjøre det bedre enn dette for søk.
Det er ingen bedre måte å bli en bedre algoritme designer enn å ha en dyp forståelse og forståelse for algoritmer.
La oss si at arrayet ditt har 50 000 oppføringer, og du har brute-force-søk (det vil si søk ved å iterere hele arrayet). Oppføringen du søker etter vil i beste tilfelle være den første oppføringen i 50.000-oppføringsarrangementet. I verste fall vil algoritmen imidlertid ta 50.000 ganger lengre tid å fullføre enn i beste tilfelle.
I stedet vil du søke med binær søk. Dette innebærer sortering av matrisen (som jeg vil la deg lære om alene) og deretter dele oppstillingen i halvparten, og sjekke for å se om søketallet er større eller mindre enn halvveismarkeringen i matrisen. Hvis det er større enn halvveismerket for en sortert matrise, vet vi at første halvdel kan kastes, da det søkte nummeret ikke er en del av matrisen. Vi kan også kutte mye arbeid ved å definere ytre grenser av arrayet og sjekke for å se om det søkte nummeret eksisterer utenfor disse grensene, og i så fall har vi tatt det som hadde vært en multiple iterasjon-operasjon og slått den inn i en enkelt iterasjonsoperasjon (som i brute-force algoritmen ville ha tatt 50.000 operasjoner).
sortedHaystack = recursiveSort (haystack); funksjon bSøk (nål, sortertHjelpest, førsteIterasjon) if (firstIteration) hvis (nål> sortertHaystack.last | < sortedHaystack.first) return false; if (haystack.length == 2) if (needle == haystack[0]) return haystack[0]; else return haystack[1]; if (needle < haystack[haystack.length/2]) bSearch(needle, haystack[0… haystack.length/2 -1], false); else bSearch(needle, haystack[haystack.length/2… haystack.length], false);
Ta den tilsynelatende kompliserte karakteren til en enkelt binær søkealgoritme, og bruk den på milliarder av mulige koblinger (som søker gjennom Google). Utover det, la oss bruke en slags rangeringssystem til de koblede søkene for å gi en ordre av responssider. Enda bedre, bruk en slags tilsynelatende randomisert "forslag" -system basert på kunstig intelligens sosiale modeller designet for å identifisere hvem du kanskje vil legge til som en venn.
Dette gir oss en mye klarere forståelse av hvorfor algoritmer er mer enn bare et fancy navn på funksjoner. På deres fineste er de klare og effektive måter å gjøre noe på som krever et høyere intuisjonsnivå enn den mest tilsynelatende løsningen. De kan ta det som kan kreve en supercomputer år å gjøre og gjøre det til en oppgave som fullfører i sekunder på en mobiltelefon.
For de fleste av oss som utviklere utarbeider vi ikke daglige abstrakte algoritmer på høyt nivå.
Heldigvis står vi på skuldrene til utviklerne som kom foran oss, som skrev innfødte sorteringsfunksjoner og tillater oss å søke strenge for substrings med indexOf på en effektiv måte.
Men vi gjør det imidlertid med våre egne algoritmer. Vi lager til
sløyfer og skrivefunksjoner hver dag; så hvordan kan gode algoritme design prinsipper informere skriving av disse funksjonene?
Et av hovedprinsippene for algoritmisk design er å bygge algoritmen din på en slik måte at selve inngangen gjør noe av arbeidet for deg. Hvis du for eksempel vet at innspillet ditt alltid kommer til å være tall, trenger du ikke å ha unntak / sjekker for strenger eller tvinge verdiene dine til tall. Hvis du vet at DOM-elementet ditt er det samme hver gang i a til
loop i JavaScript, bør du ikke spørre for det elementet i hver iterasjon. På samme måte, i din til
sløyfer, bør du ikke bruke bekvemmelighetsfunksjoner med overhead hvis du kan oppnå det samme ved hjelp av (nærmere) enkle operasjoner.
// gjør ikke dette: for (var i = 1000; i> 0; i -) $ ("# foo").Bar"); // gjør dette i stedet var foo = $ (" # foo "); var s =" "; for (var i = 1000; i> 0; i -) s + ="Bar"; foo.append (s);
Hvis du er en JavaScript-utvikler (og du bruker jQuery), og du ikke vet hva de ovennevnte funksjonene gjør, og hvordan de er vesentlig forskjellige, er neste punkt for deg.
I deres fineste er [algoritmer] klare og effektive måter å gjøre noe på som krever et høyere intuisjonsnivå enn den mest synlige løsningen.
Det er lett å tenke at dette sier seg selv. Det er imidlertid en forskjell mellom "å vite hvordan du skriver jQuery" og "forstå jQuery". Å forstå verktøyene dine betyr at du forstår hva hver linje av kode gjør, både umiddelbart (verdien av en funksjon eller effekten av en metode) og implisitt (hvor mye overhead er knyttet til å kjøre en biblioteksfunksjon, eller som er den mest effektive metode for sammenkobling av en streng). For å skrive gode algoritmer er det viktig å vite ytelsen til lavere nivåfunksjoner eller verktøy, ikke bare navnet og implementeringen av dem.
Å designe effektive algoritmer er et full-forpliktelsesoppdrag. Utover å forstå verktøyene dine som et frittstående stykke, må du også forstå hvordan de samhandler med det større systemet ved hånden. For eksempel, for å forstå JavaScript i et bestemt program helt, er det viktig å forstå DOM og ytelse av JavaScript i kryssleser-scenarier, hvor tilgjengelig minne påvirker gjengivelseshastigheter, strukturen på servere (og deres svar) du kanskje interagerer med, så vel som en myriade av andre hensyn som er immaterielle, som bruksscenarier.
Generelt er målet med algoritmedesign å fullføre en jobb i færre trinn. (Det er noen unntak, for eksempel Bcrypt hashing.) Når du skriver koden, tar du hensyn til det alle av de enkle operasjonene datamaskinen tar for å nå målet. Her er en enkel sjekkliste for å komme i gang på vei til mer effektiv algoritmdesign:
Det er ingen bedre måte å bli en bedre algoritme designer enn å ha en dyp forståelse og forståelse for algoritmer.
.sortere()
, med lavere nivå operasjoner. Hvis du ikke visste hva en algoritme var i begynnelsen av denne artikkelen, forhåpentligvis, nå, har du en mer konkret forståelse av den noe uklarheten. Som profesjonelle utviklere er det viktig at vi forstår at koden vi skriver kan analyseres og optimaliseres, og det er viktig at vi tar deg tid til å gjøre denne analysen av ytelsen til koden vår.
Eventuelle morsomme algoritme praksis problemer du har funnet? Kanskje en dynamisk programmering "knapsack problem" eller "drunken walk"? Eller kanskje du vet om noen gode rekursjonsmetoder i Ruby som avviger fra de samme funksjonene som implementeres i Python. Del dem i kommentarene!